From 03719b228509594805e797542b960bb3223eef13 Mon Sep 17 00:00:00 2001 From: Ziyang Li Date: Wed, 12 Apr 2023 01:11:10 -0400 Subject: [PATCH] Adding pacman maze experiment --- .../compiler/front/analyzers/aggregation.rs | 23 ++ core/src/compiler/front/ast/formula.rs | 32 +- core/src/compiler/front/grammar.lalrpop | 18 + core/tests/compiler/errors.rs | 10 + core/tests/integrate/basic.rs | 16 + etc/scallopy/Cargo.toml | 2 +- etc/scallopy/examples/sum_2_forward.py | 2 +- etc/scallopy/scallopy/context.py | 6 + etc/scallopy/scallopy/forward.py | 12 + etc/scallopy/scallopy/input_mapping.py | 5 + etc/scallopy/scallopy/provenance.py | 56 ++- etc/scallopy/src/context.rs | 10 +- etc/scallopy/src/custom_tag.rs | 10 +- etc/scallopy/src/foreign_function.rs | 12 +- etc/scallopy/src/tuple.rs | 2 +- etc/sclc/src/main.rs | 40 -- examples/.gitignore | 1 + examples/datalog/all_cube_is_blue.scl | 5 + examples/datalog/count_where.scl | 24 ++ examples/datalog/edge_path.scl | 4 + examples/datalog/edge_path_undir.scl | 4 + examples/datalog/evaluate_formula.scl | 33 ++ .../{demo_scl => datalog}/exists_blue_obj.scl | 0 .../fib_dt_1.scl => datalog/fibonacci.scl} | 0 examples/datalog/kinship.scl | 21 + examples/datalog/pacman_maze_example.scl | 53 +++ examples/datalog/regex.scl | 48 +++ .../{demo_scl => datalog}/type_inference.scl | 0 examples/demo_scl/all_cube_is_blue.scl | 0 examples/demo_scl/linked_list.scl | 0 .../bug_scl/arity-mismatch-1/1.scl | 0 .../bug_scl/arity-mismatch-1/2.scl | 0 examples/legacy/bug_scl/char_at_error/bug.scl | 3 + examples/legacy/bug_scl/expr_test_1/bug.scl | 9 + .../{ => legacy}/bug_scl/io-issue-18/bug.scl | 0 .../{ => legacy}/bug_scl/io-issue-23/ok.scl | 0 .../{ => legacy}/bug_scl/io-issue-3/bad-1.scl | 0 .../{ => legacy}/bug_scl/io-issue-3/good.scl | 0 .../{ => legacy}/bug_scl/io-issue-4/bug.scl | 0 .../{ => legacy}/bug_scl/io-issue-7/bug.scl | 0 .../{ => legacy}/bug_scl/io-issue-7/ok.scl | 0 examples/{ => legacy}/bug_scl/readme.md | 0 .../demo_scl/agent_pathfinding.scl | 6 +- examples/legacy/demo_scl/all_cube_is_blue.scl | 5 + .../legacy/demo_scl/avoiding_enemy_arena.scl | 53 +++ .../demo_scl/avoiding_enemy_arena_prob.scl | 50 +++ .../{ => legacy}/demo_scl/count_where.scl | 0 examples/{ => legacy}/demo_scl/datalog.scl | 0 .../{ => legacy}/demo_scl/disjunction.scl | 0 .../{ => legacy}/demo_scl/edge_path_csv_1.scl | 2 +- .../{ => legacy}/demo_scl/edge_path_csv_2.scl | 2 +- .../{ => legacy}/demo_scl/edge_path_dt_1.scl | 0 examples/legacy/demo_scl/exists_blue_obj.scl | 13 + examples/legacy/demo_scl/fib_dt_1.scl | 4 + examples/legacy/demo_scl/kinship.scl | 21 + examples/{ => legacy}/demo_scl/lambda.scl | 0 examples/legacy/demo_scl/linked_list.scl | 16 + .../{ => legacy}/demo_scl/mnist_add_sub.scl | 0 examples/legacy/demo_scl/multi_digit_hwf.scl | 49 +++ .../{ => legacy}/demo_scl/music_theory.scl | 0 examples/{ => legacy}/demo_scl/negate_1.scl | 0 .../demo_scl/no_green_between.scl | 0 examples/{ => legacy}/demo_scl/output_1.scl | 2 +- .../{ => legacy}/demo_scl/prob_rule_1.scl | 0 .../{ => legacy}/demo_scl/query_atom_2.scl | 0 examples/{ => legacy}/demo_scl/range_dt_1.scl | 0 examples/legacy/demo_scl/regex.scl | 47 +++ examples/legacy/demo_scl/sat_1.scl | 22 ++ examples/legacy/demo_scl/sat_2.scl | 21 + examples/{ => legacy}/demo_scl/stdlib.scl | 0 .../{ => legacy}/demo_scl/stdlib_usage_1.scl | 0 examples/legacy/demo_scl/type_inference.scl | 52 +++ .../{ => legacy}/demo_scl/undir_edge_path.scl | 0 examples/{ => legacy}/good_scl/animal.scl | 0 examples/{ => legacy}/good_scl/bmi.scl | 0 examples/{ => legacy}/good_scl/bmi_2.scl | 0 .../legacy/good_scl/categorical_sample.scl | 7 + .../good_scl/count_digit_3_or_4.scl | 0 examples/legacy/good_scl/date_time_1.scl | 91 +++++ examples/{ => legacy}/good_scl/digit_sum.scl | 0 .../{ => legacy}/good_scl/digit_sum_2.scl | 0 .../{ => legacy}/good_scl/digit_sum_prob.scl | 0 .../{ => legacy}/good_scl/double_dice.scl | 0 examples/{ => legacy}/good_scl/edge_path.scl | 0 .../{ => legacy}/good_scl/edge_path_2.scl | 0 examples/{ => legacy}/good_scl/expr.scl | 0 examples/{ => legacy}/good_scl/expr_parse.scl | 0 examples/{ => legacy}/good_scl/expr_prob.scl | 0 examples/{ => legacy}/good_scl/fib.scl | 0 examples/{ => legacy}/good_scl/fib_dt.scl | 0 examples/{ => legacy}/good_scl/forall_1.scl | 0 examples/{ => legacy}/good_scl/forall_3.scl | 0 .../{ => legacy}/good_scl/has_three_objs.scl | 0 examples/legacy/good_scl/hashing_1.scl | 2 + examples/legacy/good_scl/hashing_3.scl | 1 + examples/legacy/good_scl/how_many_3.scl | 2 + .../legacy/good_scl/how_many_3_with_disj.scl | 4 + examples/{ => legacy}/good_scl/hwf.scl | 0 .../{ => legacy}/good_scl/hwf_parsing.scl | 0 examples/{ => legacy}/good_scl/implies.scl | 0 examples/legacy/good_scl/ite_1.scl | 2 + examples/legacy/good_scl/kinship_ic_1.scl | 4 + examples/legacy/good_scl/negate_query_2.scl | 3 + .../good_scl/no_incoming_edge.scl | 0 examples/{ => legacy}/good_scl/obj_color.scl | 0 .../{ => legacy}/good_scl/obj_color_2.scl | 0 .../{ => legacy}/good_scl/obj_color_3.scl | 0 examples/{ => legacy}/good_scl/odd_even.scl | 0 examples/{ => legacy}/good_scl/odd_even_2.scl | 0 examples/{ => legacy}/good_scl/odd_even_3.scl | 0 examples/legacy/good_scl/path_top2proofs.scl | 20 + .../{ => legacy}/good_scl/prov_fixpoint.scl | 0 examples/{ => legacy}/good_scl/query_only.scl | 0 examples/legacy/good_scl/sample_top_1.scl | 2 + examples/legacy/good_scl/sample_top_2.scl | 7 + examples/{ => legacy}/good_scl/spectrl.scl | 0 examples/{ => legacy}/good_scl/srl_1.scl | 0 .../{ => legacy}/good_scl/student_grade_1.scl | 0 .../{ => legacy}/good_scl/student_grade_2.scl | 0 examples/legacy/good_scl/sum_1.scl | 2 + examples/{ => legacy}/good_scl/temporal_1.scl | 0 examples/{ => legacy}/good_scl/temporal_2.scl | 0 examples/{ => legacy}/input_csv/edge.csv | 0 examples/{ => legacy}/input_csv/edge_prob.csv | 0 .../invalid_scl/arity_mismatch.scl | 2 +- .../conflicting_constant_decl_type.scl | 3 + .../{ => legacy}/invalid_scl/dup_input.scl | 0 .../invalid_scl/dup_type_decl.scl | 0 .../invalid_scl/dup_type_decl_2.scl | 0 examples/legacy/invalid_scl/hashing_2.scl | 2 + .../invalid_scl/invalid_input_ext.scl | 0 .../invalid_scl/invalid_query.scl | 0 .../invalid_scl/invalid_query_type.scl | 0 .../invalid_scl/invalid_wildcard_1.scl | 0 .../invalid_scl/odd_even_non_stratified.scl | 0 .../invalid_scl/type_error_arith.scl | 0 .../invalid_scl/type_error_bad_cmp.scl | 0 .../invalid_scl/type_error_bad_const.scl | 0 .../invalid_scl/type_error_cast.scl | 0 .../invalid_scl/type_error_constraint.scl | 0 .../invalid_scl/type_error_count.scl | 0 .../invalid_scl/type_error_not.scl | 0 .../invalid_scl/type_error_num_cmp.scl | 0 .../invalid_scl/type_error_rela.scl | 0 .../{ => legacy}/invalid_scl/unbound_1.scl | 0 .../{ => legacy}/invalid_scl/unbound_2.scl | 0 .../{ => legacy}/invalid_scl/unbound_3.scl | 0 .../invalid_scl/undeclared_relation.scl | 2 + .../{ => legacy}/invalid_scl/unknown_type.scl | 0 .../{ => legacy}/tutorial_scl/graph_algo.scl | 0 .../tutorial_scl/graph_algo_autograder.py | 0 .../tutorial_scl/graph_algo_example.scl | 0 .../{ => legacy}/tutorial_scl/relations.scl | 0 .../{ => legacy}/tutorial_scl/scene_graph.scl | 0 .../tutorial_scl/scene_graph_example_1.scl | 0 .../visual_question_answering.scl | 0 examples/probabilistic/alarm.scl | 4 + examples/probabilistic/digit_less_than.scl | 29 ++ examples/probabilistic/digit_sum_2.scl | 29 ++ experiments/mnist/docs/+_964.jpg | Bin 1034 -> 0 bytes experiments/mnist/docs/3_226.jpg | Bin 1335 -> 0 bytes experiments/mnist/docs/5_42754.jpg | Bin 1364 -> 0 bytes experiments/mnist/how_many_3.py | 16 +- experiments/mnist/how_many_3_or_4.py | 15 +- experiments/mnist/plot_confusion_matrix.py | 62 +++ experiments/mnist/sort_2/run.py | 11 +- experiments/mnist/sum_2.py | 21 +- experiments/pacman_maze/arena.py | 216 +++++++++++ experiments/pacman_maze/examples/arena_1.scl | 25 ++ experiments/pacman_maze/examples/arena_2.scl | 25 ++ experiments/pacman_maze/examples/arena_3.scl | 26 ++ experiments/pacman_maze/examples/arena_4.scl | 31 ++ experiments/pacman_maze/res/agent.png | Bin 0 -> 6142 bytes experiments/pacman_maze/res/back.webp | Bin 0 -> 165582 bytes experiments/pacman_maze/res/enemy1.webp | Bin 0 -> 4508 bytes experiments/pacman_maze/res/enemy2.webp | Bin 0 -> 4296 bytes experiments/pacman_maze/res/flag.png | Bin 0 -> 5913 bytes experiments/pacman_maze/run.py | 366 ++++++++++++++++++ experiments/pacman_maze/run_demo.py | 93 +++++ experiments/pacman_maze/run_dqn.py | 255 ++++++++++++ experiments/pacman_maze/run_old.py | 322 +++++++++++++++ experiments/pacman_maze/run_random.py | 45 +++ experiments/pacman_maze/run_scallop_sanity.py | 87 +++++ experiments/pacman_maze/scl/arena.scl | 21 + .../pacman_maze/scl/arena_w_constraint.scl | 31 ++ experiments/pacman_maze/scl/grid_node.scl | 5 + 186 files changed, 2581 insertions(+), 136 deletions(-) delete mode 100644 etc/sclc/src/main.rs create mode 100644 examples/datalog/all_cube_is_blue.scl create mode 100644 examples/datalog/count_where.scl create mode 100644 examples/datalog/edge_path.scl create mode 100644 examples/datalog/edge_path_undir.scl create mode 100644 examples/datalog/evaluate_formula.scl rename examples/{demo_scl => datalog}/exists_blue_obj.scl (100%) rename examples/{demo_scl/fib_dt_1.scl => datalog/fibonacci.scl} (100%) create mode 100644 examples/datalog/kinship.scl create mode 100644 examples/datalog/pacman_maze_example.scl create mode 100644 examples/datalog/regex.scl rename examples/{demo_scl => datalog}/type_inference.scl (100%) delete mode 100644 examples/demo_scl/all_cube_is_blue.scl delete mode 100644 examples/demo_scl/linked_list.scl rename examples/{ => legacy}/bug_scl/arity-mismatch-1/1.scl (100%) rename examples/{ => legacy}/bug_scl/arity-mismatch-1/2.scl (100%) create mode 100644 examples/legacy/bug_scl/char_at_error/bug.scl create mode 100644 examples/legacy/bug_scl/expr_test_1/bug.scl rename examples/{ => legacy}/bug_scl/io-issue-18/bug.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-23/ok.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-3/bad-1.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-3/good.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-4/bug.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-7/bug.scl (100%) rename examples/{ => legacy}/bug_scl/io-issue-7/ok.scl (100%) rename examples/{ => legacy}/bug_scl/readme.md (100%) rename examples/{ => legacy}/demo_scl/agent_pathfinding.scl (89%) create mode 100644 examples/legacy/demo_scl/all_cube_is_blue.scl create mode 100644 examples/legacy/demo_scl/avoiding_enemy_arena.scl create mode 100644 examples/legacy/demo_scl/avoiding_enemy_arena_prob.scl rename examples/{ => legacy}/demo_scl/count_where.scl (100%) rename examples/{ => legacy}/demo_scl/datalog.scl (100%) rename examples/{ => legacy}/demo_scl/disjunction.scl (100%) rename examples/{ => legacy}/demo_scl/edge_path_csv_1.scl (83%) rename examples/{ => legacy}/demo_scl/edge_path_csv_2.scl (88%) rename examples/{ => legacy}/demo_scl/edge_path_dt_1.scl (100%) create mode 100644 examples/legacy/demo_scl/exists_blue_obj.scl create mode 100644 examples/legacy/demo_scl/fib_dt_1.scl create mode 100644 examples/legacy/demo_scl/kinship.scl rename examples/{ => legacy}/demo_scl/lambda.scl (100%) create mode 100644 examples/legacy/demo_scl/linked_list.scl rename examples/{ => legacy}/demo_scl/mnist_add_sub.scl (100%) create mode 100644 examples/legacy/demo_scl/multi_digit_hwf.scl rename examples/{ => legacy}/demo_scl/music_theory.scl (100%) rename examples/{ => legacy}/demo_scl/negate_1.scl (100%) rename examples/{ => legacy}/demo_scl/no_green_between.scl (100%) rename examples/{ => legacy}/demo_scl/output_1.scl (92%) rename examples/{ => legacy}/demo_scl/prob_rule_1.scl (100%) rename examples/{ => legacy}/demo_scl/query_atom_2.scl (100%) rename examples/{ => legacy}/demo_scl/range_dt_1.scl (100%) create mode 100644 examples/legacy/demo_scl/regex.scl create mode 100644 examples/legacy/demo_scl/sat_1.scl create mode 100644 examples/legacy/demo_scl/sat_2.scl rename examples/{ => legacy}/demo_scl/stdlib.scl (100%) rename examples/{ => legacy}/demo_scl/stdlib_usage_1.scl (100%) create mode 100644 examples/legacy/demo_scl/type_inference.scl rename examples/{ => legacy}/demo_scl/undir_edge_path.scl (100%) rename examples/{ => legacy}/good_scl/animal.scl (100%) rename examples/{ => legacy}/good_scl/bmi.scl (100%) rename examples/{ => legacy}/good_scl/bmi_2.scl (100%) create mode 100644 examples/legacy/good_scl/categorical_sample.scl rename examples/{ => legacy}/good_scl/count_digit_3_or_4.scl (100%) create mode 100644 examples/legacy/good_scl/date_time_1.scl rename examples/{ => legacy}/good_scl/digit_sum.scl (100%) rename examples/{ => legacy}/good_scl/digit_sum_2.scl (100%) rename examples/{ => legacy}/good_scl/digit_sum_prob.scl (100%) rename examples/{ => legacy}/good_scl/double_dice.scl (100%) rename examples/{ => legacy}/good_scl/edge_path.scl (100%) rename examples/{ => legacy}/good_scl/edge_path_2.scl (100%) rename examples/{ => legacy}/good_scl/expr.scl (100%) rename examples/{ => legacy}/good_scl/expr_parse.scl (100%) rename examples/{ => legacy}/good_scl/expr_prob.scl (100%) rename examples/{ => legacy}/good_scl/fib.scl (100%) rename examples/{ => legacy}/good_scl/fib_dt.scl (100%) rename examples/{ => legacy}/good_scl/forall_1.scl (100%) rename examples/{ => legacy}/good_scl/forall_3.scl (100%) rename examples/{ => legacy}/good_scl/has_three_objs.scl (100%) create mode 100644 examples/legacy/good_scl/hashing_1.scl create mode 100644 examples/legacy/good_scl/hashing_3.scl create mode 100644 examples/legacy/good_scl/how_many_3.scl create mode 100644 examples/legacy/good_scl/how_many_3_with_disj.scl rename examples/{ => legacy}/good_scl/hwf.scl (100%) rename examples/{ => legacy}/good_scl/hwf_parsing.scl (100%) rename examples/{ => legacy}/good_scl/implies.scl (100%) create mode 100644 examples/legacy/good_scl/ite_1.scl create mode 100644 examples/legacy/good_scl/kinship_ic_1.scl create mode 100644 examples/legacy/good_scl/negate_query_2.scl rename examples/{ => legacy}/good_scl/no_incoming_edge.scl (100%) rename examples/{ => legacy}/good_scl/obj_color.scl (100%) rename examples/{ => legacy}/good_scl/obj_color_2.scl (100%) rename examples/{ => legacy}/good_scl/obj_color_3.scl (100%) rename examples/{ => legacy}/good_scl/odd_even.scl (100%) rename examples/{ => legacy}/good_scl/odd_even_2.scl (100%) rename examples/{ => legacy}/good_scl/odd_even_3.scl (100%) create mode 100644 examples/legacy/good_scl/path_top2proofs.scl rename examples/{ => legacy}/good_scl/prov_fixpoint.scl (100%) rename examples/{ => legacy}/good_scl/query_only.scl (100%) create mode 100644 examples/legacy/good_scl/sample_top_1.scl create mode 100644 examples/legacy/good_scl/sample_top_2.scl rename examples/{ => legacy}/good_scl/spectrl.scl (100%) rename examples/{ => legacy}/good_scl/srl_1.scl (100%) rename examples/{ => legacy}/good_scl/student_grade_1.scl (100%) rename examples/{ => legacy}/good_scl/student_grade_2.scl (100%) create mode 100644 examples/legacy/good_scl/sum_1.scl rename examples/{ => legacy}/good_scl/temporal_1.scl (100%) rename examples/{ => legacy}/good_scl/temporal_2.scl (100%) rename examples/{ => legacy}/input_csv/edge.csv (100%) rename examples/{ => legacy}/input_csv/edge_prob.csv (100%) rename examples/{ => legacy}/invalid_scl/arity_mismatch.scl (56%) create mode 100644 examples/legacy/invalid_scl/conflicting_constant_decl_type.scl rename examples/{ => legacy}/invalid_scl/dup_input.scl (100%) rename examples/{ => legacy}/invalid_scl/dup_type_decl.scl (100%) rename examples/{ => legacy}/invalid_scl/dup_type_decl_2.scl (100%) create mode 100644 examples/legacy/invalid_scl/hashing_2.scl rename examples/{ => legacy}/invalid_scl/invalid_input_ext.scl (100%) rename examples/{ => legacy}/invalid_scl/invalid_query.scl (100%) rename examples/{ => legacy}/invalid_scl/invalid_query_type.scl (100%) rename examples/{ => legacy}/invalid_scl/invalid_wildcard_1.scl (100%) rename examples/{ => legacy}/invalid_scl/odd_even_non_stratified.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_arith.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_bad_cmp.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_bad_const.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_cast.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_constraint.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_count.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_not.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_num_cmp.scl (100%) rename examples/{ => legacy}/invalid_scl/type_error_rela.scl (100%) rename examples/{ => legacy}/invalid_scl/unbound_1.scl (100%) rename examples/{ => legacy}/invalid_scl/unbound_2.scl (100%) rename examples/{ => legacy}/invalid_scl/unbound_3.scl (100%) create mode 100644 examples/legacy/invalid_scl/undeclared_relation.scl rename examples/{ => legacy}/invalid_scl/unknown_type.scl (100%) rename examples/{ => legacy}/tutorial_scl/graph_algo.scl (100%) rename examples/{ => legacy}/tutorial_scl/graph_algo_autograder.py (100%) rename examples/{ => legacy}/tutorial_scl/graph_algo_example.scl (100%) rename examples/{ => legacy}/tutorial_scl/relations.scl (100%) rename examples/{ => legacy}/tutorial_scl/scene_graph.scl (100%) rename examples/{ => legacy}/tutorial_scl/scene_graph_example_1.scl (100%) rename examples/{ => legacy}/tutorial_scl/visual_question_answering.scl (100%) create mode 100644 examples/probabilistic/alarm.scl create mode 100644 examples/probabilistic/digit_less_than.scl create mode 100644 examples/probabilistic/digit_sum_2.scl delete mode 100644 experiments/mnist/docs/+_964.jpg delete mode 100644 experiments/mnist/docs/3_226.jpg delete mode 100644 experiments/mnist/docs/5_42754.jpg create mode 100644 experiments/mnist/plot_confusion_matrix.py create mode 100644 experiments/pacman_maze/arena.py create mode 100644 experiments/pacman_maze/examples/arena_1.scl create mode 100644 experiments/pacman_maze/examples/arena_2.scl create mode 100644 experiments/pacman_maze/examples/arena_3.scl create mode 100644 experiments/pacman_maze/examples/arena_4.scl create mode 100644 experiments/pacman_maze/res/agent.png create mode 100644 experiments/pacman_maze/res/back.webp create mode 100644 experiments/pacman_maze/res/enemy1.webp create mode 100644 experiments/pacman_maze/res/enemy2.webp create mode 100644 experiments/pacman_maze/res/flag.png create mode 100644 experiments/pacman_maze/run.py create mode 100644 experiments/pacman_maze/run_demo.py create mode 100644 experiments/pacman_maze/run_dqn.py create mode 100644 experiments/pacman_maze/run_old.py create mode 100644 experiments/pacman_maze/run_random.py create mode 100644 experiments/pacman_maze/run_scallop_sanity.py create mode 100644 experiments/pacman_maze/scl/arena.scl create mode 100644 experiments/pacman_maze/scl/arena_w_constraint.scl create mode 100644 experiments/pacman_maze/scl/grid_node.scl diff --git a/core/src/compiler/front/analyzers/aggregation.rs b/core/src/compiler/front/analyzers/aggregation.rs index fd30528..8a1637b 100644 --- a/core/src/compiler/front/analyzers/aggregation.rs +++ b/core/src/compiler/front/analyzers/aggregation.rs @@ -43,6 +43,21 @@ impl NodeVisitor for AggregationAnalysis { } } } + + // Check the binding variables + if reduce.bindings().is_empty() { + match &reduce.operator().node { + ReduceOperatorNode::Exists + | ReduceOperatorNode::Forall + | ReduceOperatorNode::Unknown(_) => {} + r => { + self.errors.push(AggregationAnalysisError::EmptyBinding { + agg: r.to_string(), + loc: reduce.location().clone(), + }) + } + } + } } } @@ -51,6 +66,7 @@ pub enum AggregationAnalysisError { NonMinMaxAggregationHasArgument { op: ReduceOperator }, UnknownAggregator { agg: String, loc: Loc }, ForallBodyNotImplies { loc: Loc }, + EmptyBinding { agg: String, loc: Loc }, } impl FrontCompileErrorTrait for AggregationAnalysisError { @@ -76,6 +92,13 @@ impl FrontCompileErrorTrait for AggregationAnalysisError { loc.report(src) ) } + Self::EmptyBinding { agg, loc } => { + format!( + "the binding variables of `{}` aggregation cannot be empty\n{}", + agg, + loc.report(src), + ) + } } } } diff --git a/core/src/compiler/front/ast/formula.rs b/core/src/compiler/front/ast/formula.rs index 72bd2e2..0bba8ad 100644 --- a/core/src/compiler/front/ast/formula.rs +++ b/core/src/compiler/front/ast/formula.rs @@ -269,6 +269,24 @@ pub enum ReduceOperatorNode { Unknown(String), } +impl ReduceOperatorNode { + pub fn to_string(&self) -> String { + match self { + Self::Count => "count".to_string(), + Self::Sum => "sum".to_string(), + Self::Prod => "prod".to_string(), + Self::Min => "min".to_string(), + Self::Max => "max".to_string(), + Self::Exists => "exists".to_string(), + Self::Forall => "forall".to_string(), + Self::Unique => "unique".to_string(), + Self::TopK(k) => format!("top<{}>", k), + Self::CategoricalK(k) => format!("categorical<{}>", k), + Self::Unknown(_) => "unknown".to_string(), + } + } +} + /// A reduce opeartor, e.g. `count` pub type ReduceOperator = AstNode; @@ -312,19 +330,7 @@ impl ReduceOperator { } pub fn to_string(&self) -> String { - match &self.node { - ReduceOperatorNode::Count => "count".to_string(), - ReduceOperatorNode::Sum => "sum".to_string(), - ReduceOperatorNode::Prod => "prod".to_string(), - ReduceOperatorNode::Min => "min".to_string(), - ReduceOperatorNode::Max => "max".to_string(), - ReduceOperatorNode::Exists => "exists".to_string(), - ReduceOperatorNode::Forall => "forall".to_string(), - ReduceOperatorNode::Unique => "unique".to_string(), - ReduceOperatorNode::TopK(k) => format!("top<{}>", k), - ReduceOperatorNode::CategoricalK(k) => format!("categorical<{}>", k), - ReduceOperatorNode::Unknown(_) => "unknown".to_string(), - } + self.node.to_string() } } diff --git a/core/src/compiler/front/grammar.lalrpop b/core/src/compiler/front/grammar.lalrpop index c9c39fc..88dbaa6 100644 --- a/core/src/compiler/front/grammar.lalrpop +++ b/core/src/compiler/front/grammar.lalrpop @@ -583,6 +583,16 @@ ReduceGroupBy: (Vec, Box) = { ReduceAssignmentSymbol = { "=", ":=" } ReduceNode: ReduceNode = { + ReduceAssignmentSymbol "(" ")" => { + ReduceNode { + left: vs, + operator: op, + args: args, + bindings: vec![], + body: Box::new(f), + group_by: g, + } + }, ReduceAssignmentSymbol "(" > ":" ")" => { ReduceNode { left: vs, @@ -605,6 +615,14 @@ ForallExistsReduceOpNode: ReduceOperatorNode = { ForallExistsReduceOp = Spanned; ForallExistsReduceNode: ForallExistsReduceNode = { + "(" ")" => { + ForallExistsReduceNode { + operator: op, + bindings: vec![], + body: Box::new(f), + group_by: g, + } + }, "(" > ":" ")" => { ForallExistsReduceNode { operator: op, diff --git a/core/tests/compiler/errors.rs b/core/tests/compiler/errors.rs index 7a4120d..c9cc24e 100644 --- a/core/tests/compiler/errors.rs +++ b/core/tests/compiler/errors.rs @@ -128,3 +128,13 @@ fn bad_enum_type_decl() { |e| e.contains("has already been assigned"), ) } + +#[test] +fn bad_no_binding_agg_1() { + expect_front_compile_failure( + r#" + rel r() = x := count(edge(1, 3)) + "#, + |e| e.contains("binding variables of `count` aggregation cannot be empty"), + ) +} diff --git a/core/tests/integrate/basic.rs b/core/tests/integrate/basic.rs index fbaf6d0..0da2220 100644 --- a/core/tests/integrate/basic.rs +++ b/core/tests/integrate/basic.rs @@ -660,6 +660,22 @@ fn test_count_with_where_clause() { ) } +#[test] +fn test_exists_path_1() { + expect_interpret_multi_result( + r#" + rel edge = {(0, 1), (1, 2)} + rel path(x, y) = edge(x, y) or (path(x, z) and edge(z, y)) + rel result1(b) = b := exists(path(0, 2)) + rel result2(b) = b := exists(path(0, 3)) + "#, + vec![ + ("result1", vec![(true,)].into()), + ("result2", vec![(false,)].into()), + ], + ) +} + #[test] fn test_exists_with_where_clause() { expect_interpret_multi_result( diff --git a/etc/scallopy/Cargo.toml b/etc/scallopy/Cargo.toml index aad9f1b..7c1abcd 100644 --- a/etc/scallopy/Cargo.toml +++ b/etc/scallopy/Cargo.toml @@ -13,5 +13,5 @@ sclc-core = { path = "../sclc" } rayon = "1.5" [dependencies.pyo3] -version = "0.16.5" +version = "0.18.2" features = ["extension-module"] diff --git a/etc/scallopy/examples/sum_2_forward.py b/etc/scallopy/examples/sum_2_forward.py index fb9f56c..845d5c4 100644 --- a/etc/scallopy/examples/sum_2_forward.py +++ b/etc/scallopy/examples/sum_2_forward.py @@ -8,7 +8,7 @@ compute_sum_2 = scallopy.ScallopForwardFunction( program=sum_2_program, - provenance="diffminmaxprob", + provenance="diffaddmultprob2", input_mappings={"digit_a": list(range(10)), "digit_b": list(range(10))}, output_mappings={"sum_2": list(range(19))}) diff --git a/etc/scallopy/scallopy/context.py b/etc/scallopy/scallopy/context.py index 1694baf..81ff03d 100644 --- a/etc/scallopy/scallopy/context.py +++ b/etc/scallopy/scallopy/context.py @@ -577,6 +577,12 @@ def has_input_mapping(self, relation: str) -> bool: return True return False + def set_sample_topk_facts(self, relation: str, amount: int): + if relation in self._input_mappings: + self._input_mappings[relation].set_sample_topk_facts(amount) + else: + raise Exception(f"Unknown relation {relation}") + def requires_tag(self) -> bool: """ Returns whether the context requires facts to be associated with tags diff --git a/etc/scallopy/scallopy/forward.py b/etc/scallopy/scallopy/forward.py index 441510d..4975cf4 100644 --- a/etc/scallopy/scallopy/forward.py +++ b/etc/scallopy/scallopy/forward.py @@ -1,4 +1,5 @@ from typing import Dict, Union, List, Optional, Tuple, Any, Callable +import logging import os import sys import zipfile @@ -31,6 +32,7 @@ def __init__( early_discard: Optional[bool] = None, iter_limit: Optional[int] = None, retain_graph: bool = False, + retain_topk: Optional[Dict[str, int]] = None, jit: bool = False, jit_name: str = "", jit_recompile: bool = False, @@ -68,6 +70,11 @@ def __init__( for (relation, mapping) in input_mappings.items(): self.ctx.set_input_mapping(relation, mapping) + # Set the retain top-k + if retain_topk is not None: + for (relation, k) in retain_topk.items(): + self.ctx.set_sample_topk_facts(relation, k) + # Add input facts if specified if facts is not None: for (relation, elems) in facts.items(): @@ -130,6 +137,11 @@ def __init__( self.recompile = recompile self.fn_counter = self.FORWARD_FN_COUNTER + # Preprocess the dispatch + if self.ctx.provenance == "custom" and self.dispatch == "parallel": + logging.warning("custom provenance does not support parallel dispatch; falling back to serial dispatch. Consider creating the forward function using `dispatch=\"serial\"`.") + self.dispatch = "serial" + # Populate the output and output mapping fields self._process_output_mapping(output, output_mapping, output_mappings) for output_relation in self.outputs: diff --git a/etc/scallopy/scallopy/input_mapping.py b/etc/scallopy/scallopy/input_mapping.py index df5aeed..814dbea 100644 --- a/etc/scallopy/scallopy/input_mapping.py +++ b/etc/scallopy/scallopy/input_mapping.py @@ -68,6 +68,11 @@ def __init__( if not (0 <= self.sample_dim < self.dimension): raise Exception(f"Invalid sampling dimension {self.sample_dim}; total dimension is {self.dimension}") + def set_sample_topk_facts(self, amount: int): + self.retain_k = amount + self.sample_dim = None + self.sample_strategy = "top" + def __getitem__(self, index) -> Tuple: """Get the tuple of the input mapping from an index""" if self._kind == "dict": diff --git a/etc/scallopy/scallopy/provenance.py b/etc/scallopy/scallopy/provenance.py index 829d59c..4e8dff8 100644 --- a/etc/scallopy/scallopy/provenance.py +++ b/etc/scallopy/scallopy/provenance.py @@ -6,42 +6,48 @@ class ScallopProvenance: """ Base class for a provenance context. Any class implementing `ScallopProvenance` must override the following functions: - - `base` - `zero` - `one` - `add` - `mult` + - `negate` + - `saturate` """ - def base(self, info): + def tagging_fn(self, input_tag): """ - Given the base information, generate a tag for the tuple. - Base information is specified as `I1`, `I2`, ... in the following example: + Given the input tags, generate an internal tag for the tuple. + For example, in the following code, internal tags are the I1, I2, ...: ``` python ctx.add_facts("RELA", [(I1, T1), (I2, T2), ...]) ``` - This `base` function should take in info like `I1` and return the base tag. + This `tagging_fn` function should take in input tags like `I1` and return an internal tag + + If not implemented, we assume the input tags are the internal tags. + If the input tag is not provided (`None`), we use the `one()` as the internal tag. """ - return info + if input_tag is None: return self.one() + else: return input_tag - def disjunction_base(self, infos): + def recover_fn(self, internal_tag): """ - Given a set of base informations associated with a set of tuples forming a - disjunction, return the list of tags associated with each of them. + Given an internal tag, recover the output tag. + + If not implemented, we assume the internal tags are the output tags. """ - return [self.base(i) for i in infos] + return internal_tag - def is_valid(self, tag): + def discard(self, tag): """ - Check if a given tag is valid. + Check if a given tag needs to be discarded. When a tag is invalid, the tuple associated will be removed during reasoning. The default implementation assumes every tag is valid. An example of an invalid tag: a probability tag of probability 0.0 """ - return True + return False def zero(self): """ @@ -67,6 +73,12 @@ def mult(self, t1, t2): """ raise Exception("Not implemented") + def negate(self, t): + """ + Perform semiring negation on a tag (`t`) + """ + raise Exception("Not implemented") + def aggregate_count(self, elems): """ Aggregate a count of the given elements @@ -111,12 +123,6 @@ def __init__(self): if not torch_importer.has_pytorch: raise Exception("PyTorch unavailable. You can use this semiring only with PyTorch") - def base(self, info: torch_importer.Tensor): - """ - If a torch tensor is provided then keep that tensor as the tag; otherwise we give it 1.0 - """ - return info if info is not None else self.one() - def zero(self): """ Zero tag is a floating point 0.0 (i.e. 0.0 probability being true) @@ -165,12 +171,6 @@ def __init__(self): if not torch_importer.has_pytorch: raise Exception("PyTorch unavailable. You can use this semiring only with PyTorch") - def base(self, info: torch_importer.Tensor): - """ - If a torch tensor is provided then keep that tensor as the tag; otherwise we give it 1.0 - """ - return info if info is not None else self.one() - def zero(self): """ Zero tag is a floating point 0.0 (i.e. 0.0 probability being true) @@ -219,12 +219,6 @@ def __init__(self): if not torch_importer.has_pytorch: raise Exception("PyTorch unavailable. You can use this semiring only with PyTorch") - def base(self, info: torch_importer.Tensor): - """ - If a torch tensor is provided then keep that tensor as the tag; otherwise we give it 1.0 - """ - return info if info is not None else self.one() - def zero(self): """ Zero tag is a floating point 0.0 (i.e. 0.0 probability being true) diff --git a/etc/scallopy/src/context.rs b/etc/scallopy/src/context.rs index c73abd8..f93930b 100644 --- a/etc/scallopy/src/context.rs +++ b/etc/scallopy/src/context.rs @@ -83,7 +83,7 @@ impl Context { /// * `k` - an unsigned integer serving as the hyper-parameter for provenance such as `"topkproofs"` /// * `custom_provenance` - an optional python object serving as the provenance context #[new] - #[args(provenance = "\"unit\"", k = "3", custom_provenance = "None")] + #[pyo3(signature=(provenance="unit", k=3, custom_provenance=None))] fn new(provenance: &str, k: usize, custom_provenance: Option>) -> Result { // Check provenance type match provenance { @@ -316,7 +316,7 @@ impl Context { /// # ctx = Context::new("unit", 3, None).unwrap(); /// ctx.add_relation("atom(usize, usize)", None, None).unwrap(); /// ``` - #[args(load_csv = "None", demand = "None")] + #[pyo3(signature=(relation, load_csv=None, demand=None))] fn add_relation( &mut self, relation: &str, @@ -360,7 +360,7 @@ impl Context { } /// Add a rule - #[args(tag = "None", demand = "None")] + #[pyo3(signature=(rule, tag=None, demand=None))] fn add_rule(&mut self, rule: &str, tag: Option<&PyAny>, demand: Option) -> Result<(), BindingError> { // Attributes let mut attrs = Vec::new(); @@ -491,7 +491,7 @@ impl Context { /// Get the number of relations in this context. /// If `include_hidden` is set `true`, the result will also count the hidden relations - #[args(include_hidden = false)] + #[pyo3(signature=(include_hidden = false))] fn num_relations(&self, include_hidden: bool) -> usize { if include_hidden { match_context!(&self.ctx, c, c.num_all_relations()) @@ -502,7 +502,7 @@ impl Context { /// Get a list of relations in this context. /// If `include_hidden` is set `true`, the result will also include the hidden relations - #[args(include_hidden = false)] + #[pyo3(signature=(include_hidden = false))] fn relations(&self, include_hidden: bool) -> Vec { if include_hidden { match_context!(&self.ctx, c, c.all_relations()) diff --git a/etc/scallopy/src/custom_tag.rs b/etc/scallopy/src/custom_tag.rs index 6f362e9..c9e5743 100644 --- a/etc/scallopy/src/custom_tag.rs +++ b/etc/scallopy/src/custom_tag.rs @@ -1,10 +1,12 @@ use pyo3::{Py, PyAny, Python}; use scallop_core::runtime::provenance; +/// The custom tag which holds an arbitrary python object. #[derive(Clone, Debug)] pub struct CustomTag(pub Py); impl CustomTag { + /// Create a new custom tag with a python object. pub fn new(tag: Py) -> Self { Self(tag) } @@ -16,8 +18,10 @@ impl std::fmt::Display for CustomTag { } } +/// Custom tag is a tag impl provenance::Tag for CustomTag {} +/// The custom provenance which is a wrapper of a python class #[derive(Clone, Debug)] pub struct CustomProvenance(pub Py); @@ -32,13 +36,14 @@ impl provenance::Provenance for CustomProvenance { "scallopy-custom" } + /// Invoking the provenance's tagging function on the input tag fn tagging_fn(&self, i: Self::InputTag) -> Self::Tag { Python::with_gil(|py| { - let result = self.0.call_method(py, "tagging_fn", (i,), None).unwrap(); - Self::Tag::new(result) + Self::Tag::new(self.0.call_method(py, "tagging_fn", (i,), None).unwrap()) }) } + /// Invoking the provenance's recover function on an internal tag fn recover_fn(&self, t: &Self::Tag) -> Self::OutputTag { Python::with_gil(|py| { self @@ -50,6 +55,7 @@ impl provenance::Provenance for CustomProvenance { }) } + /// Invoking the provenance's discard function on an internal tag fn discard(&self, t: &Self::Tag) -> bool { Python::with_gil(|py| { self diff --git a/etc/scallopy/src/foreign_function.rs b/etc/scallopy/src/foreign_function.rs index 8cc7954..2020f5f 100644 --- a/etc/scallopy/src/foreign_function.rs +++ b/etc/scallopy/src/foreign_function.rs @@ -42,7 +42,7 @@ impl ForeignFunction for PythonForeignFunction { .getattr(py, "generic_type_params") .expect("Cannot get foreign function generic type parameters"); let generic_type_params: &PyList = generic_type_params - .cast_as::(py) + .downcast::(py) .expect("Cannot cast into PyList"); generic_type_params.len() }) @@ -55,7 +55,7 @@ impl ForeignFunction for PythonForeignFunction { .getattr(py, "generic_type_params") .expect("Cannot get foreign function generic type parameters"); let generic_type_params: &PyList = generic_type_params - .cast_as::(py) + .downcast::(py) .expect("Cannot cast into PyList"); let param: String = generic_type_params .get_item(i) @@ -80,7 +80,7 @@ impl ForeignFunction for PythonForeignFunction { .ff .getattr(py, "static_arg_types") .expect("Cannot get foreign function static arg types"); - let static_arg_types: &PyList = static_arg_types.cast_as::(py).expect("Cannot cast into PyList"); + let static_arg_types: &PyList = static_arg_types.downcast::(py).expect("Cannot cast into PyList"); static_arg_types.len() }) } @@ -91,7 +91,7 @@ impl ForeignFunction for PythonForeignFunction { .ff .getattr(py, "static_arg_types") .expect("Cannot get foreign function static arg types"); - let static_arg_types: &PyList = static_arg_types.cast_as::(py).expect("Cannot cast into PyList"); + let static_arg_types: &PyList = static_arg_types.downcast::(py).expect("Cannot cast into PyList"); let param_type: PyObject = static_arg_types .get_item(i) .expect("Cannot get i-th param") @@ -108,7 +108,7 @@ impl ForeignFunction for PythonForeignFunction { .getattr(py, "optional_arg_types") .expect("Cannot get foreign function optional arg types"); let optional_arg_types: &PyList = optional_arg_types - .cast_as::(py) + .downcast::(py) .expect("Cannot cast into PyList"); optional_arg_types.len() }) @@ -121,7 +121,7 @@ impl ForeignFunction for PythonForeignFunction { .getattr(py, "optional_arg_types") .expect("Cannot get foreign function optional arg types"); let optional_arg_types: &PyList = optional_arg_types - .cast_as::(py) + .downcast::(py) .expect("Cannot cast into PyList"); let param_type: PyObject = optional_arg_types .get_item(i) diff --git a/etc/scallopy/src/tuple.rs b/etc/scallopy/src/tuple.rs index 8cd205c..ad3cb0e 100644 --- a/etc/scallopy/src/tuple.rs +++ b/etc/scallopy/src/tuple.rs @@ -12,7 +12,7 @@ use scallop_core::utils; pub fn from_python_tuple(v: &PyAny, ty: &TupleType) -> PyResult { match ty { TupleType::Tuple(ts) => { - let tup: &PyTuple = v.cast_as()?; + let tup: &PyTuple = v.downcast()?; if tup.len() == ts.len() { let elems = ts .iter() diff --git a/etc/sclc/src/main.rs b/etc/sclc/src/main.rs deleted file mode 100644 index a191f37..0000000 --- a/etc/sclc/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -#![feature(path_file_prefix)] - -mod exec; -mod options; -mod py; - -use structopt::StructOpt; - -use scallop_core::compiler; - -fn main() { - // Command line arguments - let opt = options::Options::from_args(); - - // Compile - let compile_opt = compiler::CompileOptions::from(&opt); - let ram = match compiler::compile_file_to_ram_with_options(&opt.input, &compile_opt) { - Ok(ram) => ram, - Err(errs) => { - for err in errs { - println!("{}", err); - } - return; - } - }; - - // Turn the ram module into a sequence of rust tokens - let module = ram.to_rs_module(&compile_opt); - - // Print the module string if debugging - if opt.debug_rs { - println!("{}", module); - } - - // Depending on the mode, create artifacts - match opt.mode.as_str() { - "executable" => exec::create_executable(&opt, &ram, module), - m => panic!("Unknown compilation mode --mode `{}`", m), - }; -} diff --git a/examples/.gitignore b/examples/.gitignore index df54770..fafdea3 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,2 +1,3 @@ output_csv temp_scl +playground diff --git a/examples/datalog/all_cube_is_blue.scl b/examples/datalog/all_cube_is_blue.scl new file mode 100644 index 0000000..590dfb0 --- /dev/null +++ b/examples/datalog/all_cube_is_blue.scl @@ -0,0 +1,5 @@ +rel obj = {0, 1, 2} +rel shape = {(0, "cube"), (1, "sphere"), (2, "cube")} +rel color = {(0, "blue"), (1, "red"), (2, "blue")} + +rel result(b) :- b = forall(o: shape(o, "cube") => color(o, "blue")) diff --git a/examples/datalog/count_where.scl b/examples/datalog/count_where.scl new file mode 100644 index 0000000..71d47c2 --- /dev/null +++ b/examples/datalog/count_where.scl @@ -0,0 +1,24 @@ +// There are three classes +rel classes = {0, 1, 2} + +// There are 6 students, 2 in each class +rel student = { + (0, "tom"), (0, "jenny"), // Class 0 + (1, "alice"), (1, "bob"), // Class 1 + (2, "jerry"), (2, "john"), // Class 2 +} + +// Each student is enrolled in a course (Math or CS) +rel enroll = { + ("tom", "CS"), ("jenny", "Math"), // Class 0 + ("alice", "CS"), ("bob", "CS"), // Class 1 + ("jerry", "Math"), ("john", "Math"), // Class 2 +} + +// Count how many student enrolls in CS course in each class +rel count_enroll_cs_in_class(c, n) :- + n = count(s: student(c, s), enroll(s, "CS") where c: classes(c)) + +// Expected: {(0, 1), (1, 2), (2, 0)} +// Interpretation: class 0 has 1 student enroll in CS, class 1 has 2, class 2 has 0 +query count_enroll_cs_in_class diff --git a/examples/datalog/edge_path.scl b/examples/datalog/edge_path.scl new file mode 100644 index 0000000..0d977dc --- /dev/null +++ b/examples/datalog/edge_path.scl @@ -0,0 +1,4 @@ +rel edge = {(0, 1), (1, 2), (2, 3)} +rel path(a, b) = edge(a, b) +rel path(a, c) = path(a, b) and edge(b, c) +query path diff --git a/examples/datalog/edge_path_undir.scl b/examples/datalog/edge_path_undir.scl new file mode 100644 index 0000000..e91a048 --- /dev/null +++ b/examples/datalog/edge_path_undir.scl @@ -0,0 +1,4 @@ +rel edge = {(0, 1), (1, 2), (2, 3)} +rel path(a, b) = edge(a, b) or (edge(a, c) and path(c, b)) +rel undir_path(a, b) = path(a, b) \/ path(b, a) +query undir_path diff --git a/examples/datalog/evaluate_formula.scl b/examples/datalog/evaluate_formula.scl new file mode 100644 index 0000000..122c38b --- /dev/null +++ b/examples/datalog/evaluate_formula.scl @@ -0,0 +1,33 @@ +// Inputs +type symbol(usize, String) +type length(usize) + +// Facts for lexing +rel digit = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} + +type term(value: f32, begin: usize, end: usize) +rel term(x as f32, b, b + 1) = symbol(b, x) and digit(x) + +type mult_div(value: f32, begin: usize, end: usize) +rel mult_div(x, b, r) = term(x, b, r) +rel mult_div(x * y, b, e) = mult_div(x, b, m) and symbol(m, "*") and term(y, m + 1, e) +rel mult_div(x / y, b, e) = mult_div(x, b, m) and symbol(m, "/") and term(y, m + 1, e) + +type add_minus(value: f32, begin: usize, end: usize) +rel add_minus(x, b, r) = mult_div(x, b, r) +rel add_minus(x + y, b, e) = add_minus(x, b, m) and symbol(m, "+") and mult_div(y, m + 1, e) +rel add_minus(x - y, b, e) = add_minus(x, b, m) and symbol(m, "-") and mult_div(y, m + 1, e) + +type result(value: f32) +rel result(y) = add_minus(y, 0, l) and length(l) + +// =============================================== // + +// Testing related +type test_string(String) +rel length($string_length(s)) = test_string(s) +rel symbol(0, $string_char_at(s, 0) as String) = test_string(s), $string_length(s) > 0 +rel symbol(i, $string_char_at(s, i) as String) = symbol(i - 1, _), test_string(s), $string_length(s) > i + +rel test_string("123/24+1") +query result diff --git a/examples/demo_scl/exists_blue_obj.scl b/examples/datalog/exists_blue_obj.scl similarity index 100% rename from examples/demo_scl/exists_blue_obj.scl rename to examples/datalog/exists_blue_obj.scl diff --git a/examples/demo_scl/fib_dt_1.scl b/examples/datalog/fibonacci.scl similarity index 100% rename from examples/demo_scl/fib_dt_1.scl rename to examples/datalog/fibonacci.scl diff --git a/examples/datalog/kinship.scl b/examples/datalog/kinship.scl new file mode 100644 index 0000000..398e276 --- /dev/null +++ b/examples/datalog/kinship.scl @@ -0,0 +1,21 @@ +const MOTHER = 1 +const FATHER = 2 +const GRANDMOTHER = 3 +const GRANDFATHER = 4 + +rel transitive = { + (MOTHER, MOTHER, GRANDMOTHER), + (FATHER, FATHER, GRANDFATHER), + (FATHER, MOTHER, GRANDMOTHER), + (MOTHER, FATHER, GRANDFATHER), +} + +rel context = { + (FATHER, "bob", "john"), + (MOTHER, "john", "alice"), +} + +rel derived(r, a, b) :- context(r, a, b) +rel derived(r3, a, b) :- derived(r1, a, c), derived(r2, c, b), transitive(r1, r2, r3) + +query derived(r, "bob", "alice") diff --git a/examples/datalog/pacman_maze_example.scl b/examples/datalog/pacman_maze_example.scl new file mode 100644 index 0000000..241f464 --- /dev/null +++ b/examples/datalog/pacman_maze_example.scl @@ -0,0 +1,53 @@ +// Input from neural networks +type grid_node(x: usize, y: usize) +type curr_position(x: usize, y: usize) +type goal_position(x: usize, y: usize) +type is_enemy(x: usize, y: usize) + +// Constants +const UP = 0 +const RIGHT = 1 +const DOWN = 2 +const LEFT = 3 + +// Basic connectivity +rel node(x, y) = grid_node(x, y), not is_enemy(x, y) +rel edge(x, y, x, yp, UP) = node(x, y), node(x, yp), yp == y + 1 +rel edge(x, y, xp, y, RIGHT) = node(x, y), node(xp, y), xp == x + 1 +rel edge(x, y, x, yp, DOWN) = node(x, y), node(x, yp), yp == y - 1 +rel edge(x, y, xp, y, LEFT) = node(x, y), node(xp, y), xp == x - 1 + +// Path for connectivity; will condition on no enemy on the path +rel path(x, y, x, y) = node(x, y) +rel path(x, y, xp, yp) = edge(x, y, xp, yp, _) +rel path(x, y, xpp, ypp) = path(x, y, xp, yp), edge(xp, yp, xpp, ypp, _) + +// Get the next position +rel next_position(a, xp, yp) = curr_position(x, y), edge(x, y, xp, yp, a) +rel action_score(a) = next_position(a, x, y), goal_position(gx, gy), path(x, y, gx, gy) + +// ============ EXAMPLE ============ + +// The following example denotes the following arena +// +// * - - +// E E - +// x - - +// +// where "*" is the goal and "x" is the current position. +// The agent needs to avoid the enemies ("E") +// and therefore the best action is to go RIGHT (represented by integer 1) + +rel grid_node = { + (0, 2), (1, 2), (2, 2), + (0, 1), (1, 1), (2, 1), + (0, 0), (1, 0), (2, 0), +} + +rel is_enemy = {(0, 1), (1, 1)} + +rel goal_position = {(0, 2)} + +rel curr_position = {(0, 0)} + +query action_score diff --git a/examples/datalog/regex.scl b/examples/datalog/regex.scl new file mode 100644 index 0000000..3de7797 --- /dev/null +++ b/examples/datalog/regex.scl @@ -0,0 +1,48 @@ +// =========== REGEX =========== +type regex_char(id: usize, c: char) +type regex_concat(id: usize, left: usize, right: usize) +type regex_union(id: usize, left: usize, right: usize) +type regex_star(id: usize, child: usize) +type regex_root(id: usize) + +// Match a single char +rel matches_substr(expr, start, start + 1) :- regex_char(expr, c), char_at(start, c) + +// Match a concatenation +rel matches_substr(expr, l, r) :- regex_concat(expr, le, re), matches_substr(le, l, m), matches_substr(re, m, r) + +// Match a union +rel matches_substr(expr, l, r) :- regex_union(expr, a, b), matches_substr(a, l, r) +rel matches_substr(expr, l, r) :- regex_union(expr, a, b), matches_substr(b, l, r) + +// Match a star +rel matches_substr(expr, i, i) :- regex_star(expr, _), range(0, l + 1, i), input_string(s), strlen(s, l) +rel matches_substr(expr, l, r) :- regex_star(expr, c), matches_substr(c, l, r) +rel matches_substr(expr, l, r) :- regex_star(expr, c), matches_substr(expr, l, m), matches_substr(c, m, r) + +// Matches the whole string +rel matches(true) :- input_string(s), strlen(s, l), regex_root(e), matches_substr(e, 0, l) + +// =========== STRING =========== +type input_string(s: String) +rel char_at(i, $string_char_at(s, i)) :- input_string(s), strlen(s, l), range(0, l, i) + +// =========== HELPER =========== +@demand("bbf") +rel range(a, b, i) :- i == a +rel range(a, b, i) :- range(a, b, i - 1), i < b +@demand("bf") +rel strlen(s, i) :- i == $string_length(s) + +// =========== EXAMPLE =========== +rel regex_char(0, 'a') +rel regex_char(1, 'b') +rel regex_concat(2, 0, 1) +rel regex_concat(3, 2, 0) +rel regex_star(4, 1) +rel regex_concat(5, 3, 4) +rel regex_root(5) + +rel input_string("ababbbb") + +query matches diff --git a/examples/demo_scl/type_inference.scl b/examples/datalog/type_inference.scl similarity index 100% rename from examples/demo_scl/type_inference.scl rename to examples/datalog/type_inference.scl diff --git a/examples/demo_scl/all_cube_is_blue.scl b/examples/demo_scl/all_cube_is_blue.scl deleted file mode 100644 index e69de29..0000000 diff --git a/examples/demo_scl/linked_list.scl b/examples/demo_scl/linked_list.scl deleted file mode 100644 index e69de29..0000000 diff --git a/examples/bug_scl/arity-mismatch-1/1.scl b/examples/legacy/bug_scl/arity-mismatch-1/1.scl similarity index 100% rename from examples/bug_scl/arity-mismatch-1/1.scl rename to examples/legacy/bug_scl/arity-mismatch-1/1.scl diff --git a/examples/bug_scl/arity-mismatch-1/2.scl b/examples/legacy/bug_scl/arity-mismatch-1/2.scl similarity index 100% rename from examples/bug_scl/arity-mismatch-1/2.scl rename to examples/legacy/bug_scl/arity-mismatch-1/2.scl diff --git a/examples/legacy/bug_scl/char_at_error/bug.scl b/examples/legacy/bug_scl/char_at_error/bug.scl new file mode 100644 index 0000000..4d876d2 --- /dev/null +++ b/examples/legacy/bug_scl/char_at_error/bug.scl @@ -0,0 +1,3 @@ +rel input("1357") +rel string_char_at(0, $string_char_at(s, 0)) :- input(s), 0 < $string_length(s) +rel string_char_at(i, $string_char_at(s, i)) :- input(s), i < $string_length(s), string_char_at(i - 1, _) diff --git a/examples/legacy/bug_scl/expr_test_1/bug.scl b/examples/legacy/bug_scl/expr_test_1/bug.scl new file mode 100644 index 0000000..b8a0315 --- /dev/null +++ b/examples/legacy/bug_scl/expr_test_1/bug.scl @@ -0,0 +1,9 @@ +rel eval(e, c) = constant(e, c) +rel eval(e, a + b) = binary(e, "+", l, r), eval(l, a), eval(r, b) +rel eval(e, a - b) = binary(e, "-", l, r), eval(l, a), eval(r, b) +rel result(y) = eval(e, y), goal(e) + +rel constant = { (0, 1), (1, 2), (2, 3) } +rel binary = { (3, "+", 0, 1), (4, "-", 3, 2) } +rel goal(4) +query result \ No newline at end of file diff --git a/examples/bug_scl/io-issue-18/bug.scl b/examples/legacy/bug_scl/io-issue-18/bug.scl similarity index 100% rename from examples/bug_scl/io-issue-18/bug.scl rename to examples/legacy/bug_scl/io-issue-18/bug.scl diff --git a/examples/bug_scl/io-issue-23/ok.scl b/examples/legacy/bug_scl/io-issue-23/ok.scl similarity index 100% rename from examples/bug_scl/io-issue-23/ok.scl rename to examples/legacy/bug_scl/io-issue-23/ok.scl diff --git a/examples/bug_scl/io-issue-3/bad-1.scl b/examples/legacy/bug_scl/io-issue-3/bad-1.scl similarity index 100% rename from examples/bug_scl/io-issue-3/bad-1.scl rename to examples/legacy/bug_scl/io-issue-3/bad-1.scl diff --git a/examples/bug_scl/io-issue-3/good.scl b/examples/legacy/bug_scl/io-issue-3/good.scl similarity index 100% rename from examples/bug_scl/io-issue-3/good.scl rename to examples/legacy/bug_scl/io-issue-3/good.scl diff --git a/examples/bug_scl/io-issue-4/bug.scl b/examples/legacy/bug_scl/io-issue-4/bug.scl similarity index 100% rename from examples/bug_scl/io-issue-4/bug.scl rename to examples/legacy/bug_scl/io-issue-4/bug.scl diff --git a/examples/bug_scl/io-issue-7/bug.scl b/examples/legacy/bug_scl/io-issue-7/bug.scl similarity index 100% rename from examples/bug_scl/io-issue-7/bug.scl rename to examples/legacy/bug_scl/io-issue-7/bug.scl diff --git a/examples/bug_scl/io-issue-7/ok.scl b/examples/legacy/bug_scl/io-issue-7/ok.scl similarity index 100% rename from examples/bug_scl/io-issue-7/ok.scl rename to examples/legacy/bug_scl/io-issue-7/ok.scl diff --git a/examples/bug_scl/readme.md b/examples/legacy/bug_scl/readme.md similarity index 100% rename from examples/bug_scl/readme.md rename to examples/legacy/bug_scl/readme.md diff --git a/examples/demo_scl/agent_pathfinding.scl b/examples/legacy/demo_scl/agent_pathfinding.scl similarity index 89% rename from examples/demo_scl/agent_pathfinding.scl rename to examples/legacy/demo_scl/agent_pathfinding.scl index 357aa3d..0d117da 100644 --- a/examples/demo_scl/agent_pathfinding.scl +++ b/examples/legacy/demo_scl/agent_pathfinding.scl @@ -20,7 +20,7 @@ rel next_state(b) = curr_state(a), edge(a, b) type goal(usize) // Whether we can reach the goal from next_state -rel can_reach_goal(x) = next_state(x), goal(y), reach(x, y), not enemy(x) +rel can_reach_goal() = curr_state(x), goal(y), reach(x, y), not enemy(x) ///////////// Example 1 ///////////// @@ -43,7 +43,7 @@ rel edge = { // There are enemies in 4, 5, 6 rel enemy = { 0.1::1, 0.1::2, 0.1::3, - 0.2::4, 0.9::5, 0.9::6, + 0.1::4, 0.9::5, 0.9::6, 0.1::7, 0.1::8, 0.1::9, } @@ -51,6 +51,6 @@ rel enemy = { rel goal(3) // We want to start from node 5, 7, or 9 -rel curr_state(8) +rel curr_state(9) query can_reach_goal diff --git a/examples/legacy/demo_scl/all_cube_is_blue.scl b/examples/legacy/demo_scl/all_cube_is_blue.scl new file mode 100644 index 0000000..590dfb0 --- /dev/null +++ b/examples/legacy/demo_scl/all_cube_is_blue.scl @@ -0,0 +1,5 @@ +rel obj = {0, 1, 2} +rel shape = {(0, "cube"), (1, "sphere"), (2, "cube")} +rel color = {(0, "blue"), (1, "red"), (2, "blue")} + +rel result(b) :- b = forall(o: shape(o, "cube") => color(o, "blue")) diff --git a/examples/legacy/demo_scl/avoiding_enemy_arena.scl b/examples/legacy/demo_scl/avoiding_enemy_arena.scl new file mode 100644 index 0000000..241f464 --- /dev/null +++ b/examples/legacy/demo_scl/avoiding_enemy_arena.scl @@ -0,0 +1,53 @@ +// Input from neural networks +type grid_node(x: usize, y: usize) +type curr_position(x: usize, y: usize) +type goal_position(x: usize, y: usize) +type is_enemy(x: usize, y: usize) + +// Constants +const UP = 0 +const RIGHT = 1 +const DOWN = 2 +const LEFT = 3 + +// Basic connectivity +rel node(x, y) = grid_node(x, y), not is_enemy(x, y) +rel edge(x, y, x, yp, UP) = node(x, y), node(x, yp), yp == y + 1 +rel edge(x, y, xp, y, RIGHT) = node(x, y), node(xp, y), xp == x + 1 +rel edge(x, y, x, yp, DOWN) = node(x, y), node(x, yp), yp == y - 1 +rel edge(x, y, xp, y, LEFT) = node(x, y), node(xp, y), xp == x - 1 + +// Path for connectivity; will condition on no enemy on the path +rel path(x, y, x, y) = node(x, y) +rel path(x, y, xp, yp) = edge(x, y, xp, yp, _) +rel path(x, y, xpp, ypp) = path(x, y, xp, yp), edge(xp, yp, xpp, ypp, _) + +// Get the next position +rel next_position(a, xp, yp) = curr_position(x, y), edge(x, y, xp, yp, a) +rel action_score(a) = next_position(a, x, y), goal_position(gx, gy), path(x, y, gx, gy) + +// ============ EXAMPLE ============ + +// The following example denotes the following arena +// +// * - - +// E E - +// x - - +// +// where "*" is the goal and "x" is the current position. +// The agent needs to avoid the enemies ("E") +// and therefore the best action is to go RIGHT (represented by integer 1) + +rel grid_node = { + (0, 2), (1, 2), (2, 2), + (0, 1), (1, 1), (2, 1), + (0, 0), (1, 0), (2, 0), +} + +rel is_enemy = {(0, 1), (1, 1)} + +rel goal_position = {(0, 2)} + +rel curr_position = {(0, 0)} + +query action_score diff --git a/examples/legacy/demo_scl/avoiding_enemy_arena_prob.scl b/examples/legacy/demo_scl/avoiding_enemy_arena_prob.scl new file mode 100644 index 0000000..767f832 --- /dev/null +++ b/examples/legacy/demo_scl/avoiding_enemy_arena_prob.scl @@ -0,0 +1,50 @@ +// Input from neural networks +type grid_node(x: i8, y: i8) +type is_agent(x: i8, y: i8) +type is_goal(x: i8, y: i8) +type is_enemy(x: i8, y: i8) + +// Constants +const UP = 0, RIGHT = 1, DOWN = 2, LEFT = 3 + +// Basic connectivity +rel node(x, y) = grid_node(x, y), not is_enemy(x, y) +rel edge(x, y, x, yp, UP) = node(x, y), node(x, yp), yp == y + 1 +rel edge(x, y, xp, y, RIGHT) = node(x, y), node(xp, y), xp == x + 1 +rel edge(x, y, x, yp, DOWN) = node(x, y), node(x, yp), yp == y - 1 +rel edge(x, y, xp, y, LEFT) = node(x, y), node(xp, y), xp == x - 1 + +// Path for connectivity; will condition on no enemy on the path +rel path(x, y, x, y) = node(x, y) +rel path(x, y, xp, yp) = edge(x, y, xp, yp, _) +rel path(x, y, xpp, ypp) = path(x, y, xp, yp), edge(xp, yp, xpp, ypp, _) + +// Get the next position +rel next_position(a, xp, yp) = is_agent(x, y), edge(x, y, xp, yp, a) +rel expected_reward(a) = next_position(a, x, y), is_goal(gx, gy), path(x, y, gx, gy) + +// ============ EXAMPLE ============ + +// The following example denotes the following arena +// +// g - - +// E E - +// a - - +// +// where "g" is the goal and "a" is the current position. +// The agent needs to avoid the enemies ("E") +// and therefore the best action is to go RIGHT (represented by integer 1) + +rel grid_node = { + 0.95::(0, 2), 0.95::(1, 2), 0.95::(2, 2), + 0.95::(0, 1), 0.95::(1, 1), 0.95::(2, 1), + 0.95::(0, 0), 0.95::(1, 0), 0.95::(2, 0), +} + +rel is_enemy = {0.99::(0, 1), 0.99::(1, 1)} + +rel is_agent = {0.99::(0, 0)} + +rel is_goal = {0.98::(0, 2)} + +query expected_reward diff --git a/examples/demo_scl/count_where.scl b/examples/legacy/demo_scl/count_where.scl similarity index 100% rename from examples/demo_scl/count_where.scl rename to examples/legacy/demo_scl/count_where.scl diff --git a/examples/demo_scl/datalog.scl b/examples/legacy/demo_scl/datalog.scl similarity index 100% rename from examples/demo_scl/datalog.scl rename to examples/legacy/demo_scl/datalog.scl diff --git a/examples/demo_scl/disjunction.scl b/examples/legacy/demo_scl/disjunction.scl similarity index 100% rename from examples/demo_scl/disjunction.scl rename to examples/legacy/demo_scl/disjunction.scl diff --git a/examples/demo_scl/edge_path_csv_1.scl b/examples/legacy/demo_scl/edge_path_csv_1.scl similarity index 83% rename from examples/demo_scl/edge_path_csv_1.scl rename to examples/legacy/demo_scl/edge_path_csv_1.scl index 4ab9566..849b6e5 100644 --- a/examples/demo_scl/edge_path_csv_1.scl +++ b/examples/legacy/demo_scl/edge_path_csv_1.scl @@ -1,5 +1,5 @@ @file("examples/input_csv/edge.csv") -input edge(usize, usize) +type edge(usize, usize) @demand("bf") rel path(a, c) = edge(a, c) \/ path(a, b) /\ edge(b, c) diff --git a/examples/demo_scl/edge_path_csv_2.scl b/examples/legacy/demo_scl/edge_path_csv_2.scl similarity index 88% rename from examples/demo_scl/edge_path_csv_2.scl rename to examples/legacy/demo_scl/edge_path_csv_2.scl index 5feef22..59d3a63 100644 --- a/examples/demo_scl/edge_path_csv_2.scl +++ b/examples/legacy/demo_scl/edge_path_csv_2.scl @@ -1,5 +1,5 @@ @file("examples/input_csv/edge_prob.csv", deliminator = "\t", has_header = true, has_probability = true) -input edge(usize, usize) +type edge(usize, usize) @demand("bf") rel path(a, c) = edge(a, c) \/ path(a, b) /\ edge(b, c) diff --git a/examples/demo_scl/edge_path_dt_1.scl b/examples/legacy/demo_scl/edge_path_dt_1.scl similarity index 100% rename from examples/demo_scl/edge_path_dt_1.scl rename to examples/legacy/demo_scl/edge_path_dt_1.scl diff --git a/examples/legacy/demo_scl/exists_blue_obj.scl b/examples/legacy/demo_scl/exists_blue_obj.scl new file mode 100644 index 0000000..44b76f5 --- /dev/null +++ b/examples/legacy/demo_scl/exists_blue_obj.scl @@ -0,0 +1,13 @@ +// A set of all the shapes +rel all_shapes = {"cube", "cylinder", "sphere"} + +// Each object has two attributes: color and shape +rel color = {(0, "red"), (1, "green"), (2, "blue"), (3, "blue")} +rel shape = {(0, "cube"), (1, "cylinder"), (2, "sphere"), (3, "cube")} + +// Is there a blue object? +rel exists_blue_obj(b) = b = exists(o: color(o, "blue")) + +// For each shape, is there a blue object of that shape? +rel exists_blue_obj_of_shape(s, b) :- + b = exists(o: color(o, "blue"), shape(o, s) where s: all_shapes(s)) diff --git a/examples/legacy/demo_scl/fib_dt_1.scl b/examples/legacy/demo_scl/fib_dt_1.scl new file mode 100644 index 0000000..0081095 --- /dev/null +++ b/examples/legacy/demo_scl/fib_dt_1.scl @@ -0,0 +1,4 @@ +@demand("bf") +rel fib(x, a + b) = fib(x - 1, a), fib(x - 2, b), x > 1 +rel fib = {(0, 1), (1, 1)} +query fib(10, y) diff --git a/examples/legacy/demo_scl/kinship.scl b/examples/legacy/demo_scl/kinship.scl new file mode 100644 index 0000000..398e276 --- /dev/null +++ b/examples/legacy/demo_scl/kinship.scl @@ -0,0 +1,21 @@ +const MOTHER = 1 +const FATHER = 2 +const GRANDMOTHER = 3 +const GRANDFATHER = 4 + +rel transitive = { + (MOTHER, MOTHER, GRANDMOTHER), + (FATHER, FATHER, GRANDFATHER), + (FATHER, MOTHER, GRANDMOTHER), + (MOTHER, FATHER, GRANDFATHER), +} + +rel context = { + (FATHER, "bob", "john"), + (MOTHER, "john", "alice"), +} + +rel derived(r, a, b) :- context(r, a, b) +rel derived(r3, a, b) :- derived(r1, a, c), derived(r2, c, b), transitive(r1, r2, r3) + +query derived(r, "bob", "alice") diff --git a/examples/demo_scl/lambda.scl b/examples/legacy/demo_scl/lambda.scl similarity index 100% rename from examples/demo_scl/lambda.scl rename to examples/legacy/demo_scl/lambda.scl diff --git a/examples/legacy/demo_scl/linked_list.scl b/examples/legacy/demo_scl/linked_list.scl new file mode 100644 index 0000000..04e43e2 --- /dev/null +++ b/examples/legacy/demo_scl/linked_list.scl @@ -0,0 +1,16 @@ +type LinkedList <: usize +type cons(LinkedList, i32, LinkedList) +type nil(LinkedList) + +type length(LinkedList, usize) +rel length(list, 0) :- nil(list) +rel length(list, l + 1) :- cons(list, _, tail), length(tail, l) + +// ==== Example ==== +const L1 = 0 +const L2 = 1 +const L3 = 2 +const L4 = 3 +rel nil = {L1} +rel cons = {(L2, 10, L1), (L3, 20, L2), (L4, 30, L3)} +query length(L4, l) diff --git a/examples/demo_scl/mnist_add_sub.scl b/examples/legacy/demo_scl/mnist_add_sub.scl similarity index 100% rename from examples/demo_scl/mnist_add_sub.scl rename to examples/legacy/demo_scl/mnist_add_sub.scl diff --git a/examples/legacy/demo_scl/multi_digit_hwf.scl b/examples/legacy/demo_scl/multi_digit_hwf.scl new file mode 100644 index 0000000..e0e5a77 --- /dev/null +++ b/examples/legacy/demo_scl/multi_digit_hwf.scl @@ -0,0 +1,49 @@ +// Inputs +type symbol(usize, String) +type length(usize) + +// Facts for lexing +rel digit = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} +rel mult_div = {"*", "/"} +rel plus_minus = {"+", "-"} + +// Parsing +type value_node(id: u64, string: String, begin: usize, end: usize) +rel value_node($hash(x, d), d, x, x + 1) = symbol(x, d), digit(d) +rel value_node($hash(joint, b - 1, e), joint, b - 1, e) = + symbol(b - 1, dh), digit(dh), value_node(x, dr, b, e), joint == $string_concat(dh, dr) + +type mult_div_node(id: u64, string: String, left_node: u64, right_node: u64, begin: usize, end: usize) +rel mult_div_node(id, string, 0, 0, b, e) = value_node(id, string, b, e) +rel mult_div_node($hash(id, s, l, r), s, l, r, b, e) = + symbol(id, s), mult_div(s), mult_div_node(l, _, _, _, b, id), value_node(r, _, id + 1, e) + +type plus_minus_node(id: u64, string: String, left_node: u64, right_node: u64, begin: usize, end: usize) +rel plus_minus_node(id, string, l, r, b, e) = mult_div_node(id, string, l, r, b, e) +rel plus_minus_node($hash(id, s, l, r), s, l, r, b, e) = + symbol(id, s), plus_minus(s), plus_minus_node(l, _, _, _, b, id), mult_div_node(r, _, _, _, id + 1, e) + +type root_node(id: u64) +rel root_node(id) = plus_minus_node(id, _, _, _, 0, l), length(l) + +// Evaluate AST +@demand("bf") +rel eval(x, s as f64) = value_node(x, s, _, _) +rel eval(x, y1 + y2) = plus_minus_node(x, "+", l, r, _, _), eval(l, y1), eval(r, y2) +rel eval(x, y1 - y2) = plus_minus_node(x, "-", l, r, _, _), eval(l, y1), eval(r, y2) +rel eval(x, y1 * y2) = mult_div_node(x, "*", l, r, _, _), eval(l, y1), eval(r, y2) +rel eval(x, y1 / y2) = mult_div_node(x, "/", l, r, _, _), eval(l, y1), eval(r, y2), y2 != 0.0 + +// Compute result +rel result(y) = eval(e, y), root_node(e) + +// =============================================== // + +// Testing related +type test_string(String) +rel length($string_length(s)) = test_string(s) +rel symbol(0, $string_char_at(s, 0) as String) = test_string(s), $string_length(s) > 0 +rel symbol(i, $string_char_at(s, i) as String) = symbol(i - 1, _), test_string(s), $string_length(s) > i + +rel test_string("123/24+1") +query result diff --git a/examples/demo_scl/music_theory.scl b/examples/legacy/demo_scl/music_theory.scl similarity index 100% rename from examples/demo_scl/music_theory.scl rename to examples/legacy/demo_scl/music_theory.scl diff --git a/examples/demo_scl/negate_1.scl b/examples/legacy/demo_scl/negate_1.scl similarity index 100% rename from examples/demo_scl/negate_1.scl rename to examples/legacy/demo_scl/negate_1.scl diff --git a/examples/demo_scl/no_green_between.scl b/examples/legacy/demo_scl/no_green_between.scl similarity index 100% rename from examples/demo_scl/no_green_between.scl rename to examples/legacy/demo_scl/no_green_between.scl diff --git a/examples/demo_scl/output_1.scl b/examples/legacy/demo_scl/output_1.scl similarity index 92% rename from examples/demo_scl/output_1.scl rename to examples/legacy/demo_scl/output_1.scl index fd36d8c..aa84821 100644 --- a/examples/demo_scl/output_1.scl +++ b/examples/legacy/demo_scl/output_1.scl @@ -2,4 +2,4 @@ rel edge = {(0, 1), (1, 2), (2, 3)} rel path(a, b) = edge(a, b) \/ path(a, c) /\ edge(c, b) @file("examples/output_csv/output_1_path.csv") -output path +query path diff --git a/examples/demo_scl/prob_rule_1.scl b/examples/legacy/demo_scl/prob_rule_1.scl similarity index 100% rename from examples/demo_scl/prob_rule_1.scl rename to examples/legacy/demo_scl/prob_rule_1.scl diff --git a/examples/demo_scl/query_atom_2.scl b/examples/legacy/demo_scl/query_atom_2.scl similarity index 100% rename from examples/demo_scl/query_atom_2.scl rename to examples/legacy/demo_scl/query_atom_2.scl diff --git a/examples/demo_scl/range_dt_1.scl b/examples/legacy/demo_scl/range_dt_1.scl similarity index 100% rename from examples/demo_scl/range_dt_1.scl rename to examples/legacy/demo_scl/range_dt_1.scl diff --git a/examples/legacy/demo_scl/regex.scl b/examples/legacy/demo_scl/regex.scl new file mode 100644 index 0000000..da12b7e --- /dev/null +++ b/examples/legacy/demo_scl/regex.scl @@ -0,0 +1,47 @@ +// =========== REGEX =========== +type regex_char(id: usize, c: char) +type regex_concat(id: usize, left: usize, right: usize) +type regex_star(id: usize, child: usize) +type regex_root(id: usize) + +// Match a single char +rel matches_substr(expr, start, start + 1) :- regex_char(expr, c), char_at(start, c) + +// Match a concatenation +rel matches_substr(expr, l, r) :- regex_concat(expr, le, re), matches_substr(le, l, m), matches_substr(re, m, r) + +// Match a union +rel matches_substr(expr, l, r) :- regex_union(expr, a, b), matches_substr(a, l, r) +rel matches_substr(expr, l, r) :- regex_union(expr, a, b), matches_substr(b, l, r) + +// Match a star +rel matches_substr(expr, i, i) :- regex_star(expr, _), range(0, l + 1, i), input_string(s), strlen(s, l) +rel matches_substr(expr, l, r) :- regex_star(expr, c), matches_substr(c, l, r) +rel matches_substr(expr, l, r) :- regex_star(expr, c), matches_substr(expr, l, m), matches_substr(c, m, r) + +// Matches the whole string +rel matches() :- input_string(s), strlen(s, l), regex_root(e), matches_substr(e, 0, l) + +// =========== STRING =========== +type input_string(s: String) +rel char_at(i, $string_char_at(s, i)) :- input_string(s), strlen(s, l), range(0, l, i) + +// =========== HELPER =========== +@demand("bbf") +rel range(a, b, i) :- i == a +rel range(a, b, i) :- range(a, b, i - 1), i < b +@demand("bf") +rel strlen(s, i) :- i == $string_length(s) + +// =========== EXAMPLE =========== +rel regex_char(0, 'a') +rel regex_char(1, 'b') +rel regex_concat(2, 0, 1) +rel regex_concat(3, 2, 0) +rel regex_star(4, 1) +rel regex_concat(5, 3, 4) +rel regex_root(5) + +rel input_string("ababbbb") + +query matches diff --git a/examples/legacy/demo_scl/sat_1.scl b/examples/legacy/demo_scl/sat_1.scl new file mode 100644 index 0000000..f55edb5 --- /dev/null +++ b/examples/legacy/demo_scl/sat_1.scl @@ -0,0 +1,22 @@ +type assign(String, bool) + +// Assignments to variables A, B, and C +rel assign = {1.0::("A", true); 1.0::("A", false)} +rel assign = {1.0::("B", true); 1.0::("B", false)} +rel assign = {1.0::("C", true); 1.0::("C", false)} + +// Boolean formula (A and !B) or (B and !C) +rel bf_var = {(1, "A"), (2, "B"), (3, "B"), (4, "C")} +rel bf_not = {(5, 2), (6, 4)} +rel bf_and = {(7, 1, 5), (8, 3, 6)} +rel bf_or = {(9, 7, 8)} +rel bf_root = {9} + +// Evaluation +rel eval_bf(bf, r) :- bf_var(bf, v), assign(v, r) +rel eval_bf(bf, !r) :- bf_not(bf, c), eval_bf(c, r) +rel eval_bf(bf, lr && rr) :- bf_and(bf, lbf, rbf), eval_bf(lbf, lr), eval_bf(rbf, rr) +rel eval_bf(bf, lr || rr) :- bf_or(bf, lbf, rbf), eval_bf(lbf, lr), eval_bf(rbf, rr) +rel eval(r) :- bf_root(bf), eval_bf(bf, r) + +query eval diff --git a/examples/legacy/demo_scl/sat_2.scl b/examples/legacy/demo_scl/sat_2.scl new file mode 100644 index 0000000..6516c81 --- /dev/null +++ b/examples/legacy/demo_scl/sat_2.scl @@ -0,0 +1,21 @@ +type assign(String, bool) + +// Assignments to variables A and B +rel assign = {1.0::("A", true); 1.0::("A", false)} +rel assign = {1.0::("B", true); 1.0::("B", false)} + +// Boolean formula (A and !A) or (B and !B) +rel bf_var = {(1, "A"), (2, "B")} +rel bf_not = {(3, 1), (4, 2)} +rel bf_and = {(5, 1, 3), (6, 2, 4)} +rel bf_or = {(7, 5, 6)} +rel bf_root = {7} + +// Evaluation +rel eval_bf(bf, r) :- bf_var(bf, v), assign(v, r) +rel eval_bf(bf, !r) :- bf_not(bf, c), eval_bf(c, r) +rel eval_bf(bf, lr && rr) :- bf_and(bf, lbf, rbf), eval_bf(lbf, lr), eval_bf(rbf, rr) +rel eval_bf(bf, lr || rr) :- bf_or(bf, lbf, rbf), eval_bf(lbf, lr), eval_bf(rbf, rr) +rel eval(r) :- bf_root(bf), eval_bf(bf, r) + +query eval diff --git a/examples/demo_scl/stdlib.scl b/examples/legacy/demo_scl/stdlib.scl similarity index 100% rename from examples/demo_scl/stdlib.scl rename to examples/legacy/demo_scl/stdlib.scl diff --git a/examples/demo_scl/stdlib_usage_1.scl b/examples/legacy/demo_scl/stdlib_usage_1.scl similarity index 100% rename from examples/demo_scl/stdlib_usage_1.scl rename to examples/legacy/demo_scl/stdlib_usage_1.scl diff --git a/examples/legacy/demo_scl/type_inference.scl b/examples/legacy/demo_scl/type_inference.scl new file mode 100644 index 0000000..af9970e --- /dev/null +++ b/examples/legacy/demo_scl/type_inference.scl @@ -0,0 +1,52 @@ +// EXP ::= let V = EXP in EXP +// | if EXP then EXP else EXP +// | X + Y | X - Y +// | X and Y | X or Y | not X +// | X == Y | X != Y | X < Y | X <= Y | X > Y | X >= Y + +// Basic syntax constructs +type number(usize, i32) +type boolean(usize, bool) +type variable(usize, String) +type bexp(usize, String, usize, usize) +type aexp(usize, String, usize, usize) +type let_in(usize, String, usize, usize) +type if_then_else(usize, usize, usize, usize) + +// Comparison operations +rel comparison_op = {"==", "!=", ">=", "<=", ">", "<"} +rel logical_op = {"&&", "||", "^"} +rel arith_op = {"+", "-", "*", "/"} + +// A program with each number 0-4 denoting their index +// let x = 3 in x == 4 +// -------------------0 +// -1 ------2 +// -3 -4 +rel let_in = {(0, "x", 1, 2)} +rel number = {(1, 3), (4, 4)} +rel bexp = {(2, "==", 3, 4)} +rel variable = {(3, "x")} + +// Type Inference: + +// - Base case +rel type_of(x, "bool") = boolean(x, _) +rel type_of(x, "int") = number(x, _) +rel type_of(x, t) = variable(x, v), env_type(x, v, t) +rel type_of(e, "bool") = bexp(e, op, x, y), comparison_op(op), type_of(x, "int"), type_of(y, "int") +rel type_of(e, "bool") = bexp(e, op, x, y), logical_op(op), type_of(x, "bool"), type_of(y, "bool") +rel type_of(e, "int") = aexp(e, op, x, y), arith_op(op), type_of(x, "int"), type_of(y, "int") +rel type_of(e, t) = let_in(e, v, b, c), env_type(c, v, tv), type_of(b, tv), type_of(c, t) +rel type_of(e, t) = if_then_else(e, x, y, z), type_of(x, "bool"), type_of(y, t), type_of(z, t) + +// - Environment variable type +rel env_type(x, v, t) = bexp(e, _, x, _), env_type(e, v, t) +rel env_type(y, v, t) = bexp(e, _, _, y), env_type(e, v, t) +rel env_type(x, v, t) = aexp(e, _, x, _), env_type(e, v, t) +rel env_type(y, v, t) = aexp(e, _, _, y), env_type(e, v, t) +rel env_type(z, v, t) = let_in(_, v, y, z), type_of(y, t) +rel env_type(z, v2, t) = let_in(x, v1, _, z), env_type(x, v2, t), v1 != v2 +rel env_type(x, v, t) = env_type(e, v, t), if_then_else(e, x, _, _) +rel env_type(y, v, t) = env_type(e, v, t), if_then_else(e, _, y, _) +rel env_type(z, v, t) = env_type(e, v, t), if_then_else(e, _, _, z) diff --git a/examples/demo_scl/undir_edge_path.scl b/examples/legacy/demo_scl/undir_edge_path.scl similarity index 100% rename from examples/demo_scl/undir_edge_path.scl rename to examples/legacy/demo_scl/undir_edge_path.scl diff --git a/examples/good_scl/animal.scl b/examples/legacy/good_scl/animal.scl similarity index 100% rename from examples/good_scl/animal.scl rename to examples/legacy/good_scl/animal.scl diff --git a/examples/good_scl/bmi.scl b/examples/legacy/good_scl/bmi.scl similarity index 100% rename from examples/good_scl/bmi.scl rename to examples/legacy/good_scl/bmi.scl diff --git a/examples/good_scl/bmi_2.scl b/examples/legacy/good_scl/bmi_2.scl similarity index 100% rename from examples/good_scl/bmi_2.scl rename to examples/legacy/good_scl/bmi_2.scl diff --git a/examples/legacy/good_scl/categorical_sample.scl b/examples/legacy/good_scl/categorical_sample.scl new file mode 100644 index 0000000..371866e --- /dev/null +++ b/examples/legacy/good_scl/categorical_sample.scl @@ -0,0 +1,7 @@ +const A = 0, B = 1, C = 2 + +rel obj_color = {0.4::(A, "red"); 0.3::(A, "blue"); 0.3::(A, "green")} +rel obj_color = {0.3::(B, "red"); 0.5::(B, "blue"); 0.2::(B, "green")} +rel obj_color = {0.05::(C, "red"); 0.05::(C, "blue"); 0.9::(C, "green")} + +rel sampled_obj_color(obj, c) = c := categorical<1>(c: obj_color(obj, c)) diff --git a/examples/good_scl/count_digit_3_or_4.scl b/examples/legacy/good_scl/count_digit_3_or_4.scl similarity index 100% rename from examples/good_scl/count_digit_3_or_4.scl rename to examples/legacy/good_scl/count_digit_3_or_4.scl diff --git a/examples/legacy/good_scl/date_time_1.scl b/examples/legacy/good_scl/date_time_1.scl new file mode 100644 index 0000000..6e7a4ef --- /dev/null +++ b/examples/legacy/good_scl/date_time_1.scl @@ -0,0 +1,91 @@ +rel event = {(1, t"2021-05-01T01:17:02.604456Z"), (2, t"2019-11-29 08:15:47.624504-08"), (3, t"Wed, 02 Jun 2021 06:31:39 GMT"), + (4, t"2017-07-19 03:21:51+00:00"), (5, t"2014-04-26 05:24:37 PM"), (6, t"2012-08-03 18:31:59.257000000"), + (7, t"2014-12-16 06:20:00 GMT"), (8, t"2021-02-21 PST"), (9, t"2012-08-03 18:31:59.257000000 +0000"), + (10, t"September 17, 2012, 10:10:09"), (11, t"May 6 at 9:24 PM"), (12, t"4:00pm"), + (13, t"14 May 2019 19:11:40.164"), (14, t"oct. 7, 1970"), (15, t"May 26, 2021, 12:49 AM PDT"), + (16, t"03/19/2012 10:11:59.318636"), (17, t"8/8/1965 01:00 PM"), (18, t"7 oct 70"), + (19, t"171113 14:14:20"), (20, t"03.31.2014"), (21, t"2012/03/19 10:11:59"), + (22, t"2014年04月08日11时25分18秒") + } + +rel duration = {(1, d"15 days 20 seconds 100 milliseconds"), (2, d"14 days seconds"), (3, d".:++++]][][[][15[]][seconds][]:}}}}")} + +//duration and datetime + +rel adding_duration_and_datetime_one(dr, dte, x + y) = event(dte, x), duration(dr, y) + +rel adding_duration_and_datetime_two(dr, dte, y + x) = event(dte, x), duration(dr, y) + +rel subtracting_duration_from_datetime(dr, dte, x - y) = event(dte, x), duration(dr, y) + +//duration and duration + +rel adding_durations(dr_one, dr_two, x + y) = duration(dr_one, x), duration(dr_two, y) + +rel subtracting_durations(dr_one, dr_two, x - y) = duration(dr_one, x), duration(dr_two, y) + +rel eq_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x == y + +rel neq_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x != y + +rel gt_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x > y + +rel gte_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x >= y + +rel lt_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x < y + +rel lte_durations(dr_one, dr_two) = duration(dr_one, x), duration(dr_two, y), x <= y + +//duration and int + +rel div_duration(dr, x/3) = duration(dr, x) + +rel mul_duration_three(dr, x*3) = duration(dr, x) + +rel mul_duration_four(dr, 4*x) = duration(dr, x) + +rel mul_duration_neg(dr, -1*x) = duration(dr, x) + + + +//datetime and datetime + +rel subtracting_datetimes(dt_one, dt_two, x - y) = event(dt_one, x), event(dt_two, y), x >= y + +rel eq_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x == y + +rel neq_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x != y + +rel gt_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x > y + +rel gte_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x >= y + +rel lt_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x < y + +rel lte_datetimes(dt_one, dt_two) = event(dt_one, x), event(dt_two, y), x <= y + +//aggregators for datetime + +rel how_many_datetimes(x) = x = count(a: event(_,a)) + +rel how_many_datetimes_less_than(x, y) = y = count(i: event(i,a), event(x,f), a < f) + +rel exists_a_datetime_less_than(x, y) = y = exists(i: event(i,a), event(x,f), a < f) + +rel closest_later_event(x,y) = y = min[i](dte: event(i, dte), event(x,f), dte > f) + + + +//aggregators for duration + +rel how_many_durations(x) = x = count(a: duration(_,a)) + +rel how_many_durations_less_than(x, y) = y = count(i: duration(i,a), duration(x,f), a < f) + +rel exists_durations_less_than(x, y) = y = exists(i: duration(i,a), duration(x,f), a < f) + +rel sum_durations(x) = x = sum(a: duration(_,a)) + +rel closest_later_duration(x,y) = y = min[i](dte: duration(i, dte), duration(x,f), dte > f) + + diff --git a/examples/good_scl/digit_sum.scl b/examples/legacy/good_scl/digit_sum.scl similarity index 100% rename from examples/good_scl/digit_sum.scl rename to examples/legacy/good_scl/digit_sum.scl diff --git a/examples/good_scl/digit_sum_2.scl b/examples/legacy/good_scl/digit_sum_2.scl similarity index 100% rename from examples/good_scl/digit_sum_2.scl rename to examples/legacy/good_scl/digit_sum_2.scl diff --git a/examples/good_scl/digit_sum_prob.scl b/examples/legacy/good_scl/digit_sum_prob.scl similarity index 100% rename from examples/good_scl/digit_sum_prob.scl rename to examples/legacy/good_scl/digit_sum_prob.scl diff --git a/examples/good_scl/double_dice.scl b/examples/legacy/good_scl/double_dice.scl similarity index 100% rename from examples/good_scl/double_dice.scl rename to examples/legacy/good_scl/double_dice.scl diff --git a/examples/good_scl/edge_path.scl b/examples/legacy/good_scl/edge_path.scl similarity index 100% rename from examples/good_scl/edge_path.scl rename to examples/legacy/good_scl/edge_path.scl diff --git a/examples/good_scl/edge_path_2.scl b/examples/legacy/good_scl/edge_path_2.scl similarity index 100% rename from examples/good_scl/edge_path_2.scl rename to examples/legacy/good_scl/edge_path_2.scl diff --git a/examples/good_scl/expr.scl b/examples/legacy/good_scl/expr.scl similarity index 100% rename from examples/good_scl/expr.scl rename to examples/legacy/good_scl/expr.scl diff --git a/examples/good_scl/expr_parse.scl b/examples/legacy/good_scl/expr_parse.scl similarity index 100% rename from examples/good_scl/expr_parse.scl rename to examples/legacy/good_scl/expr_parse.scl diff --git a/examples/good_scl/expr_prob.scl b/examples/legacy/good_scl/expr_prob.scl similarity index 100% rename from examples/good_scl/expr_prob.scl rename to examples/legacy/good_scl/expr_prob.scl diff --git a/examples/good_scl/fib.scl b/examples/legacy/good_scl/fib.scl similarity index 100% rename from examples/good_scl/fib.scl rename to examples/legacy/good_scl/fib.scl diff --git a/examples/good_scl/fib_dt.scl b/examples/legacy/good_scl/fib_dt.scl similarity index 100% rename from examples/good_scl/fib_dt.scl rename to examples/legacy/good_scl/fib_dt.scl diff --git a/examples/good_scl/forall_1.scl b/examples/legacy/good_scl/forall_1.scl similarity index 100% rename from examples/good_scl/forall_1.scl rename to examples/legacy/good_scl/forall_1.scl diff --git a/examples/good_scl/forall_3.scl b/examples/legacy/good_scl/forall_3.scl similarity index 100% rename from examples/good_scl/forall_3.scl rename to examples/legacy/good_scl/forall_3.scl diff --git a/examples/good_scl/has_three_objs.scl b/examples/legacy/good_scl/has_three_objs.scl similarity index 100% rename from examples/good_scl/has_three_objs.scl rename to examples/legacy/good_scl/has_three_objs.scl diff --git a/examples/legacy/good_scl/hashing_1.scl b/examples/legacy/good_scl/hashing_1.scl new file mode 100644 index 0000000..0ed0901 --- /dev/null +++ b/examples/legacy/good_scl/hashing_1.scl @@ -0,0 +1,2 @@ +rel edge = {(0, 1), (1, 2)} +rel result(c) :- c == $hash(a, b), edge(a, b) diff --git a/examples/legacy/good_scl/hashing_3.scl b/examples/legacy/good_scl/hashing_3.scl new file mode 100644 index 0000000..38be42d --- /dev/null +++ b/examples/legacy/good_scl/hashing_3.scl @@ -0,0 +1 @@ +rel result(x) = x == $hash(1, 3) diff --git a/examples/legacy/good_scl/how_many_3.scl b/examples/legacy/good_scl/how_many_3.scl new file mode 100644 index 0000000..2fb4b52 --- /dev/null +++ b/examples/legacy/good_scl/how_many_3.scl @@ -0,0 +1,2 @@ +rel digit = {0.91::(0, 0), 0.01::(0, 1), 0.01::(0, 2), 0.01::(0, 3)} +rel result(n) :- n = count(o: digit(o, 3)) diff --git a/examples/legacy/good_scl/how_many_3_with_disj.scl b/examples/legacy/good_scl/how_many_3_with_disj.scl new file mode 100644 index 0000000..2bfd158 --- /dev/null +++ b/examples/legacy/good_scl/how_many_3_with_disj.scl @@ -0,0 +1,4 @@ +rel digit = {0.91::(0, 0); 0.03::(0, 1); 0.03::(0, 2); 0.03::(0, 3)} +rel digit = {0.02::(1, 0); 0.03::(1, 1); 0.02::(1, 2); 0.93::(1, 3)} +rel digit = {0.01::(2, 0); 0.01::(2, 1); 0.01::(2, 2); 0.96::(2, 3)} +rel result(n) :- n = count(o: digit(o, 3)) diff --git a/examples/good_scl/hwf.scl b/examples/legacy/good_scl/hwf.scl similarity index 100% rename from examples/good_scl/hwf.scl rename to examples/legacy/good_scl/hwf.scl diff --git a/examples/good_scl/hwf_parsing.scl b/examples/legacy/good_scl/hwf_parsing.scl similarity index 100% rename from examples/good_scl/hwf_parsing.scl rename to examples/legacy/good_scl/hwf_parsing.scl diff --git a/examples/good_scl/implies.scl b/examples/legacy/good_scl/implies.scl similarity index 100% rename from examples/good_scl/implies.scl rename to examples/legacy/good_scl/implies.scl diff --git a/examples/legacy/good_scl/ite_1.scl b/examples/legacy/good_scl/ite_1.scl new file mode 100644 index 0000000..88d0764 --- /dev/null +++ b/examples/legacy/good_scl/ite_1.scl @@ -0,0 +1,2 @@ +rel a = {true} +rel b(x) = x == (if y then 3 else 4), a(y) diff --git a/examples/legacy/good_scl/kinship_ic_1.scl b/examples/legacy/good_scl/kinship_ic_1.scl new file mode 100644 index 0000000..c396e92 --- /dev/null +++ b/examples/legacy/good_scl/kinship_ic_1.scl @@ -0,0 +1,4 @@ +rel context = {0.9::("father", "A", "B"); 0.01::("mother", "A", "B")} +rel context = {0.7::("uncle", "B", "A"); 0.01::("son", "B", "A"); 0.01::("daughter", "B", "A")} +rel ic(r) = r = forall(a, b: context("father", a, b) => (context("son", b, a) or context("daughter", b, a))) +query ic diff --git a/examples/legacy/good_scl/negate_query_2.scl b/examples/legacy/good_scl/negate_query_2.scl new file mode 100644 index 0000000..3744742 --- /dev/null +++ b/examples/legacy/good_scl/negate_query_2.scl @@ -0,0 +1,3 @@ +rel B("Alice") +rel A() :- ~B(_) +query A diff --git a/examples/good_scl/no_incoming_edge.scl b/examples/legacy/good_scl/no_incoming_edge.scl similarity index 100% rename from examples/good_scl/no_incoming_edge.scl rename to examples/legacy/good_scl/no_incoming_edge.scl diff --git a/examples/good_scl/obj_color.scl b/examples/legacy/good_scl/obj_color.scl similarity index 100% rename from examples/good_scl/obj_color.scl rename to examples/legacy/good_scl/obj_color.scl diff --git a/examples/good_scl/obj_color_2.scl b/examples/legacy/good_scl/obj_color_2.scl similarity index 100% rename from examples/good_scl/obj_color_2.scl rename to examples/legacy/good_scl/obj_color_2.scl diff --git a/examples/good_scl/obj_color_3.scl b/examples/legacy/good_scl/obj_color_3.scl similarity index 100% rename from examples/good_scl/obj_color_3.scl rename to examples/legacy/good_scl/obj_color_3.scl diff --git a/examples/good_scl/odd_even.scl b/examples/legacy/good_scl/odd_even.scl similarity index 100% rename from examples/good_scl/odd_even.scl rename to examples/legacy/good_scl/odd_even.scl diff --git a/examples/good_scl/odd_even_2.scl b/examples/legacy/good_scl/odd_even_2.scl similarity index 100% rename from examples/good_scl/odd_even_2.scl rename to examples/legacy/good_scl/odd_even_2.scl diff --git a/examples/good_scl/odd_even_3.scl b/examples/legacy/good_scl/odd_even_3.scl similarity index 100% rename from examples/good_scl/odd_even_3.scl rename to examples/legacy/good_scl/odd_even_3.scl diff --git a/examples/legacy/good_scl/path_top2proofs.scl b/examples/legacy/good_scl/path_top2proofs.scl new file mode 100644 index 0000000..291b04f --- /dev/null +++ b/examples/legacy/good_scl/path_top2proofs.scl @@ -0,0 +1,20 @@ +// B -- C -- D +// | | | +// A E +// | | +// G -- H + +// Edges +const A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7 +rel is_enemy = {0.01::B, 0.01::C, 0.01::D, 0.99::F} +rel raw_edge = {(A, B), (B, C), (C, D), (D, E)} +rel raw_edge = {/*(A, F), (F, E)*/} +rel raw_edge = {(C, F)} +rel raw_edge = {(G, A), (G, H), (H, F)} + +// Recursive rules +rel edge(a, b) = edge(b, a) or (raw_edge(a, b) and not is_enemy(a) and not is_enemy(b)) +rel path(a, c) = edge(a, c) or (path(a, b) and edge(b, c)) + +// Query +query path(H, E) diff --git a/examples/good_scl/prov_fixpoint.scl b/examples/legacy/good_scl/prov_fixpoint.scl similarity index 100% rename from examples/good_scl/prov_fixpoint.scl rename to examples/legacy/good_scl/prov_fixpoint.scl diff --git a/examples/good_scl/query_only.scl b/examples/legacy/good_scl/query_only.scl similarity index 100% rename from examples/good_scl/query_only.scl rename to examples/legacy/good_scl/query_only.scl diff --git a/examples/legacy/good_scl/sample_top_1.scl b/examples/legacy/good_scl/sample_top_1.scl new file mode 100644 index 0000000..517a699 --- /dev/null +++ b/examples/legacy/good_scl/sample_top_1.scl @@ -0,0 +1,2 @@ +rel digit_a = {0.01::0, 0.01::1, 0.2::2, 0.3::3, 0.4::4, 0.01::5, 0.01::6, 0.01::7, 0.01::8, 0.01::9} +rel sampled_digit_a(x) :- x = top<3>(x: digit_a(x)) diff --git a/examples/legacy/good_scl/sample_top_2.scl b/examples/legacy/good_scl/sample_top_2.scl new file mode 100644 index 0000000..8c94f66 --- /dev/null +++ b/examples/legacy/good_scl/sample_top_2.scl @@ -0,0 +1,7 @@ +rel symbol = { + (0, "0"), (0, "1"), (0, "2"), (0, "3"), (0, "4"), (0, "5"), (0, "+"), (0, "-"), (0, "*"), (0, "/"), + (1, "0"), (1, "1"), (1, "2"), (1, "3"), (1, "4"), (1, "5"), (1, "+"), (1, "-"), (1, "*"), (1, "/"), + (2, "0"), (2, "1"), (2, "2"), (2, "3"), (2, "4"), (2, "5"), (2, "+"), (2, "-"), (2, "*"), (2, "/"), +} + +rel sampled_symbols(id, sym) :- sym = top<3>(s: symbol(id, s)) diff --git a/examples/good_scl/spectrl.scl b/examples/legacy/good_scl/spectrl.scl similarity index 100% rename from examples/good_scl/spectrl.scl rename to examples/legacy/good_scl/spectrl.scl diff --git a/examples/good_scl/srl_1.scl b/examples/legacy/good_scl/srl_1.scl similarity index 100% rename from examples/good_scl/srl_1.scl rename to examples/legacy/good_scl/srl_1.scl diff --git a/examples/good_scl/student_grade_1.scl b/examples/legacy/good_scl/student_grade_1.scl similarity index 100% rename from examples/good_scl/student_grade_1.scl rename to examples/legacy/good_scl/student_grade_1.scl diff --git a/examples/good_scl/student_grade_2.scl b/examples/legacy/good_scl/student_grade_2.scl similarity index 100% rename from examples/good_scl/student_grade_2.scl rename to examples/legacy/good_scl/student_grade_2.scl diff --git a/examples/legacy/good_scl/sum_1.scl b/examples/legacy/good_scl/sum_1.scl new file mode 100644 index 0000000..28b44cc --- /dev/null +++ b/examples/legacy/good_scl/sum_1.scl @@ -0,0 +1,2 @@ +rel color_num_obj :- {("blue", 1), ("red", 3), ("yellow", 6)} +rel num_obj(n) :- n = sum(y: color_num_obj(_, y)) diff --git a/examples/good_scl/temporal_1.scl b/examples/legacy/good_scl/temporal_1.scl similarity index 100% rename from examples/good_scl/temporal_1.scl rename to examples/legacy/good_scl/temporal_1.scl diff --git a/examples/good_scl/temporal_2.scl b/examples/legacy/good_scl/temporal_2.scl similarity index 100% rename from examples/good_scl/temporal_2.scl rename to examples/legacy/good_scl/temporal_2.scl diff --git a/examples/input_csv/edge.csv b/examples/legacy/input_csv/edge.csv similarity index 100% rename from examples/input_csv/edge.csv rename to examples/legacy/input_csv/edge.csv diff --git a/examples/input_csv/edge_prob.csv b/examples/legacy/input_csv/edge_prob.csv similarity index 100% rename from examples/input_csv/edge_prob.csv rename to examples/legacy/input_csv/edge_prob.csv diff --git a/examples/invalid_scl/arity_mismatch.scl b/examples/legacy/invalid_scl/arity_mismatch.scl similarity index 56% rename from examples/invalid_scl/arity_mismatch.scl rename to examples/legacy/invalid_scl/arity_mismatch.scl index acc62ac..b90ac9f 100644 --- a/examples/invalid_scl/arity_mismatch.scl +++ b/examples/legacy/invalid_scl/arity_mismatch.scl @@ -1,2 +1,2 @@ -input edge(a: usize, b: usize) +type edge(a: usize, b: usize) rel path(a, b) :- edge(a, b, c), edge(a) diff --git a/examples/legacy/invalid_scl/conflicting_constant_decl_type.scl b/examples/legacy/invalid_scl/conflicting_constant_decl_type.scl new file mode 100644 index 0000000..309e65d --- /dev/null +++ b/examples/legacy/invalid_scl/conflicting_constant_decl_type.scl @@ -0,0 +1,3 @@ +const V: usize = 5 +type r(u32, usize) +rel r = {(V, 3), (3, V)} diff --git a/examples/invalid_scl/dup_input.scl b/examples/legacy/invalid_scl/dup_input.scl similarity index 100% rename from examples/invalid_scl/dup_input.scl rename to examples/legacy/invalid_scl/dup_input.scl diff --git a/examples/invalid_scl/dup_type_decl.scl b/examples/legacy/invalid_scl/dup_type_decl.scl similarity index 100% rename from examples/invalid_scl/dup_type_decl.scl rename to examples/legacy/invalid_scl/dup_type_decl.scl diff --git a/examples/invalid_scl/dup_type_decl_2.scl b/examples/legacy/invalid_scl/dup_type_decl_2.scl similarity index 100% rename from examples/invalid_scl/dup_type_decl_2.scl rename to examples/legacy/invalid_scl/dup_type_decl_2.scl diff --git a/examples/legacy/invalid_scl/hashing_2.scl b/examples/legacy/invalid_scl/hashing_2.scl new file mode 100644 index 0000000..fd19564 --- /dev/null +++ b/examples/legacy/invalid_scl/hashing_2.scl @@ -0,0 +1,2 @@ +rel edge = {(0, 1), (1, 2)} +rel result(c) :- c == $hash(a, x), edge(a, b) diff --git a/examples/invalid_scl/invalid_input_ext.scl b/examples/legacy/invalid_scl/invalid_input_ext.scl similarity index 100% rename from examples/invalid_scl/invalid_input_ext.scl rename to examples/legacy/invalid_scl/invalid_input_ext.scl diff --git a/examples/invalid_scl/invalid_query.scl b/examples/legacy/invalid_scl/invalid_query.scl similarity index 100% rename from examples/invalid_scl/invalid_query.scl rename to examples/legacy/invalid_scl/invalid_query.scl diff --git a/examples/invalid_scl/invalid_query_type.scl b/examples/legacy/invalid_scl/invalid_query_type.scl similarity index 100% rename from examples/invalid_scl/invalid_query_type.scl rename to examples/legacy/invalid_scl/invalid_query_type.scl diff --git a/examples/invalid_scl/invalid_wildcard_1.scl b/examples/legacy/invalid_scl/invalid_wildcard_1.scl similarity index 100% rename from examples/invalid_scl/invalid_wildcard_1.scl rename to examples/legacy/invalid_scl/invalid_wildcard_1.scl diff --git a/examples/invalid_scl/odd_even_non_stratified.scl b/examples/legacy/invalid_scl/odd_even_non_stratified.scl similarity index 100% rename from examples/invalid_scl/odd_even_non_stratified.scl rename to examples/legacy/invalid_scl/odd_even_non_stratified.scl diff --git a/examples/invalid_scl/type_error_arith.scl b/examples/legacy/invalid_scl/type_error_arith.scl similarity index 100% rename from examples/invalid_scl/type_error_arith.scl rename to examples/legacy/invalid_scl/type_error_arith.scl diff --git a/examples/invalid_scl/type_error_bad_cmp.scl b/examples/legacy/invalid_scl/type_error_bad_cmp.scl similarity index 100% rename from examples/invalid_scl/type_error_bad_cmp.scl rename to examples/legacy/invalid_scl/type_error_bad_cmp.scl diff --git a/examples/invalid_scl/type_error_bad_const.scl b/examples/legacy/invalid_scl/type_error_bad_const.scl similarity index 100% rename from examples/invalid_scl/type_error_bad_const.scl rename to examples/legacy/invalid_scl/type_error_bad_const.scl diff --git a/examples/invalid_scl/type_error_cast.scl b/examples/legacy/invalid_scl/type_error_cast.scl similarity index 100% rename from examples/invalid_scl/type_error_cast.scl rename to examples/legacy/invalid_scl/type_error_cast.scl diff --git a/examples/invalid_scl/type_error_constraint.scl b/examples/legacy/invalid_scl/type_error_constraint.scl similarity index 100% rename from examples/invalid_scl/type_error_constraint.scl rename to examples/legacy/invalid_scl/type_error_constraint.scl diff --git a/examples/invalid_scl/type_error_count.scl b/examples/legacy/invalid_scl/type_error_count.scl similarity index 100% rename from examples/invalid_scl/type_error_count.scl rename to examples/legacy/invalid_scl/type_error_count.scl diff --git a/examples/invalid_scl/type_error_not.scl b/examples/legacy/invalid_scl/type_error_not.scl similarity index 100% rename from examples/invalid_scl/type_error_not.scl rename to examples/legacy/invalid_scl/type_error_not.scl diff --git a/examples/invalid_scl/type_error_num_cmp.scl b/examples/legacy/invalid_scl/type_error_num_cmp.scl similarity index 100% rename from examples/invalid_scl/type_error_num_cmp.scl rename to examples/legacy/invalid_scl/type_error_num_cmp.scl diff --git a/examples/invalid_scl/type_error_rela.scl b/examples/legacy/invalid_scl/type_error_rela.scl similarity index 100% rename from examples/invalid_scl/type_error_rela.scl rename to examples/legacy/invalid_scl/type_error_rela.scl diff --git a/examples/invalid_scl/unbound_1.scl b/examples/legacy/invalid_scl/unbound_1.scl similarity index 100% rename from examples/invalid_scl/unbound_1.scl rename to examples/legacy/invalid_scl/unbound_1.scl diff --git a/examples/invalid_scl/unbound_2.scl b/examples/legacy/invalid_scl/unbound_2.scl similarity index 100% rename from examples/invalid_scl/unbound_2.scl rename to examples/legacy/invalid_scl/unbound_2.scl diff --git a/examples/invalid_scl/unbound_3.scl b/examples/legacy/invalid_scl/unbound_3.scl similarity index 100% rename from examples/invalid_scl/unbound_3.scl rename to examples/legacy/invalid_scl/unbound_3.scl diff --git a/examples/legacy/invalid_scl/undeclared_relation.scl b/examples/legacy/invalid_scl/undeclared_relation.scl new file mode 100644 index 0000000..f81c515 --- /dev/null +++ b/examples/legacy/invalid_scl/undeclared_relation.scl @@ -0,0 +1,2 @@ +rel path(a, b) = edge(a, b) +rel path(a, c) = path(a, b), edge(b, c) diff --git a/examples/invalid_scl/unknown_type.scl b/examples/legacy/invalid_scl/unknown_type.scl similarity index 100% rename from examples/invalid_scl/unknown_type.scl rename to examples/legacy/invalid_scl/unknown_type.scl diff --git a/examples/tutorial_scl/graph_algo.scl b/examples/legacy/tutorial_scl/graph_algo.scl similarity index 100% rename from examples/tutorial_scl/graph_algo.scl rename to examples/legacy/tutorial_scl/graph_algo.scl diff --git a/examples/tutorial_scl/graph_algo_autograder.py b/examples/legacy/tutorial_scl/graph_algo_autograder.py similarity index 100% rename from examples/tutorial_scl/graph_algo_autograder.py rename to examples/legacy/tutorial_scl/graph_algo_autograder.py diff --git a/examples/tutorial_scl/graph_algo_example.scl b/examples/legacy/tutorial_scl/graph_algo_example.scl similarity index 100% rename from examples/tutorial_scl/graph_algo_example.scl rename to examples/legacy/tutorial_scl/graph_algo_example.scl diff --git a/examples/tutorial_scl/relations.scl b/examples/legacy/tutorial_scl/relations.scl similarity index 100% rename from examples/tutorial_scl/relations.scl rename to examples/legacy/tutorial_scl/relations.scl diff --git a/examples/tutorial_scl/scene_graph.scl b/examples/legacy/tutorial_scl/scene_graph.scl similarity index 100% rename from examples/tutorial_scl/scene_graph.scl rename to examples/legacy/tutorial_scl/scene_graph.scl diff --git a/examples/tutorial_scl/scene_graph_example_1.scl b/examples/legacy/tutorial_scl/scene_graph_example_1.scl similarity index 100% rename from examples/tutorial_scl/scene_graph_example_1.scl rename to examples/legacy/tutorial_scl/scene_graph_example_1.scl diff --git a/examples/tutorial_scl/visual_question_answering.scl b/examples/legacy/tutorial_scl/visual_question_answering.scl similarity index 100% rename from examples/tutorial_scl/visual_question_answering.scl rename to examples/legacy/tutorial_scl/visual_question_answering.scl diff --git a/examples/probabilistic/alarm.scl b/examples/probabilistic/alarm.scl new file mode 100644 index 0000000..db9f8c2 --- /dev/null +++ b/examples/probabilistic/alarm.scl @@ -0,0 +1,4 @@ +rel 0.05::earthquake() +rel 0.90::burglary() +rel 0.95::alarm() :- earthquake() or burglary() +query alarm() diff --git a/examples/probabilistic/digit_less_than.scl b/examples/probabilistic/digit_less_than.scl new file mode 100644 index 0000000..a7dbf85 --- /dev/null +++ b/examples/probabilistic/digit_less_than.scl @@ -0,0 +1,29 @@ +rel digit_1 = { + 0.01::0, + 0.01::1, + 0.01::2, + 0.91::3, + 0.01::4, + 0.01::5, + 0.01::6, + 0.01::7, + 0.01::8, + 0.01::9, +} + +rel digit_2 = { + 0.01::0, + 0.01::1, + 0.01::2, + 0.02::3, + 0.01::4, + 0.01::5, + 0.01::6, + 0.90::7, + 0.01::8, + 0.01::9, +} + +rel less_than(a < b) = digit_1(a) and digit_2(b) + +query less_than diff --git a/examples/probabilistic/digit_sum_2.scl b/examples/probabilistic/digit_sum_2.scl new file mode 100644 index 0000000..750ca0d --- /dev/null +++ b/examples/probabilistic/digit_sum_2.scl @@ -0,0 +1,29 @@ +rel digit_1 = { + 0.01::0, + 0.01::1, + 0.01::2, + 0.91::3, + 0.01::4, + 0.01::5, + 0.01::6, + 0.01::7, + 0.01::8, + 0.01::9, +} + +rel digit_2 = { + 0.01::0, + 0.01::1, + 0.01::2, + 0.02::3, + 0.01::4, + 0.01::5, + 0.01::6, + 0.90::7, + 0.01::8, + 0.01::9, +} + +rel sum_2(a + b) = digit_1(a) and digit_2(b) + +query sum_2 diff --git a/experiments/mnist/docs/+_964.jpg b/experiments/mnist/docs/+_964.jpg deleted file mode 100644 index d0b80f654c5efdf062be90547baef721b8085c24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcmV+l1oiv>*#F=F5K2Z#MgRc;000310RRC1+WOW1pxs80RaI300000000010s{mE1_uZU3Jd?l0JRVR0s#X90t5pE z1q1{D00Dgg0s{a95d{(Xb($mz{*4NnC+Tr5kKeGP-;idloW}k=u01-d1{Qm&#Pxwx}8)K{g0B8B$U!CNAswq6L zGs=s0+-~xdIodJuhx`;9{t5l?L*d83Z3A5RGvhys9t*pNH@-xsy%^luL= z-a62&6Fdsqxn1cWSTquf8?4gUawj(i&Z$ACU8d_VZvq$loe zEIuh)N#Q+pcmDCNuC<*S((1q0ub!6p^#sG>|#EKjAh10D`9LAMq&v0ETJ( zpZq!YuiXCt!bAT63&UL2zq0pyziR%^{{Vz$$L;=Jcm12~mcDl1FYOR+^H;C^n7?N& zN8k(|H1Q9Mek$r7G4Wr-j~FkCd}pj&>9+PZqf!3L)AY|G!qyedwTyQ9ZL&oa(%jqy zU*BBZIcZzeD6bFy0D_cwPsLs={ewO#%kbyJI)<;~FAsP==fxinEOeVEy1G6n(ZJPo zNg=mD-)fdUPR`Cnw`FvhkVf&yj>z7@;Xf95SI52;(>!(HUlVEi)`_iY@Zad#&XanV zHn;Y(Ad)MHWsH=TCq`FSQdO0K1cO=>QAHKtAMi=v`9+uP)BgZw{{Z0M{W00sztLa+ E*&8hAF#rGn diff --git a/experiments/mnist/docs/3_226.jpg b/experiments/mnist/docs/3_226.jpg deleted file mode 100644 index 7953e9367b3095511153299219b8e747d731000c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1335 zcmV-71<3mU*#F=F5K2Z#MgRc;000310RRC1+WOW1pxs80RaI300000000010s{mE1_uZU3Jd?l0JRVR0s#X90t5pE z1q1{D00Dgg0s{a95d{(Xb($mz{*4NnC+Tr5k~4A#NG^_UbEITt81jWxY8%JhfuQLZF%J-t;#%(lUdDm14sMuBzPb2>c99W9-aQ1 z;1Aft;r{@EHFW;~k53i+Ezl$Ip1Wh78|=ESjrLpXUFcdj@@7vmDI|&aM=d;Jtw zKk-Y!z9aa7;7jia_)p@;h&&CbUHM;U@lS=VwCS(xwn~oPQfgM0cBgu&jl9So3nLZU z;az+~;P?CzoAy2UQ{t@~;#Y|@e;9mC@s^FJ=`hFR4~*Xtb$=G!OLeBalG$oP9ZKd~ zOKa$6xVx4si)bNhcD}n1c`0!!H-GW3_CxqJ@w3HN{{R&3thEmed_vRo^s>>uCwMCS z>6-nWyl*7YTD{C;7uG3pC)y61dY3wU(xNo>vu(v^{?C6DymzJe?_T|vK0j-}Z1Hc0 zf71LT;!Dd|t!DAxhQwCC@i!4bt8TY8_csQ{_9!-mnm8`L&t-3_Slf?aqKYW50P(N< zPCwwOzYRaKKhnS99Q~jCasL1b581wd?7!_DbHabw{{U#)pCj#G*?OOmzaDq?b>7Y2 zXP0lk(O!T1J^Xb40EN|~>)L0CJT3nK2>$?#JQ3m#_(!~d;k3T{WIAT5EbVVX+uejk zySAFr_Ri`VwnpX~sKoHwTrBp``UMr>AF<#3UZ0A85>+O_S(w!#UdGS|W%@KwL~ zEnn?V{{RF${{Vyw{u*DvKM(#p{{X_z{?z`&{{Vy+O1|(X_8<0d**xz*?N^%L+r00$ z#kX(Syq~kaeD5@QUr2t>UI_Sw{{RIV_yyp9+9&q3@!p?z@b}^egY?f8_;=v{0E4VA t^m|yX^*dPZMTdwk#-SncEKys__Ev$SwA1dETb8-7n{~3OW1pxs80RaI300000000010s{mE1_uZU3Jd?l0JRVR0s#X90t5pE z1q1{D00Dgg0s{a95d{(Xb($mz{*4NnC+Tr5kf3nZ~6#oF@_r*O^_OAW0{{Uzo z5Bw3;^vJ+VmH;KWUxrQXNQHSSnpUg`~?J%EtkP z{e%7(N5pRE1ws=EN(l4aK85hWw;qBYZ z-q5s>!ttN*>c99W9;5!7F{8G>(@n*Yyju-7(uCw;5>#c|u zH1no^E-2%PHpdzw`u_mJ--Q1F3qA*U635|h!B2*sAMk&MG`~0dGsB)1(V)}pEj-Cy zX*{cV5hIU$vPmOtQMsL%TpRjqPwgqBP4RTSli#|qKYfW{{U|9g}yuZv->mZns3703;Scl z{u}WJhx}#X&kp^nD}U|#{R3LD*Dds)v`ot#uiGWllGE)km4odbb0mT?jnI4x{iyx~ z{?Y#c+Lm7l{B!o5UqJFd;U@5=v8G*VI_`t#`G(*7LrTAj8|^>ONS;W3(F}fcKuaF@ z*m7e?H~P@d+}qy-)YtK4?q49pB3o(oz|1C=stgzE(!@Una}@fXAwIwymEBkB^` z+IW&3RWzIZU99y>3o9FI32m0&O1~+8Y+!uJX>9f`Qf)$Cf1N z0QS@PVc?Gh{{H|^_#fg8L&hFE*RB5mc@2)MaiCr4I(C5h+P`VIwAZe6Cn(PL(AZk} z(4uO#lOKPgxc>l+e+4`%@yo)t`WCyWY8qChrD_SO_*=y~9H|zk;oVIVNaMe=l0DV{?MNcyj7?E&pr*(JYjL+ZC6In^mu$p;)!mp?QJi9+h?ol3c7u)QVYl-dwKMG zw%H8jW=EPgw)?-~Z^GXKd>8N~kHc?<+86eIk*E2s9B*|k#M8|zuQc~J6U@&w)y(rr WEc0B 0: # If can move down + self.curr_pos = (self.curr_pos[0], self.curr_pos[1] - 1) + elif action == 3 and self.curr_pos[0] > 0: # If can move left + self.curr_pos = (self.curr_pos[0] - 1, self.curr_pos[1]) + + # Check if reached goal position + done, reward = False, self.default_reward + if self.curr_pos in self.enemies: done, reward = True, self.on_failure_reward # Hitting enemy + elif self.curr_pos == self.goal_pos: done, reward = True, self.on_success_reward # Reaching goal + elif self.curr_pos == prev_pos: done, reward = False, self.remain_unchanged_reward # Stay in same position + + # Return + return ((), done, reward, ()) + + def hidden_state(self): + """ + Return a tuple (current_position, goal_position, enemy_positions) + where enemy positions is a list of enemy positions + + This hidden_state should not be used by model that desires to solve the game + """ + return (self.curr_pos, self.goal_pos, self.enemies) + + def render(self): + w, h = int(self.image_w * self.dpi), int(self.image_h * self.dpi) + # image = numpy.zeros((w, h, 3), dtype=numpy.uint8) + image = numpy.zeros((h, w, 3), dtype=numpy.uint8) + + # Setup the background + image[0:h, 0:w] = self.background_image[0:h, 0:w] + + # Draw the current position + self._paint_spirit(image, self.agent_image, self.curr_pos) + + # Draw the goal position + self._paint_spirit(image, self.goal_image, self.goal_pos) + + # Draw the enemy position + for (i, enemy_pos) in enumerate(self.enemies): + self._paint_spirit(image, self.enemy_images[self.enemy_types[i]], enemy_pos) + + return image + + def render_torch_tensor(self, image=None): + image = self.render() if image is None else image + image = numpy.ascontiguousarray(image, dtype=numpy.float32) / 255 + torch_image = torch.tensor(image).permute(2, 0, 1).float() + return torch.stack([torch_image]) + + def _paint_spirit(self, background, spirit, orig_cell_pos): + cell_pos = (orig_cell_pos[0], self.grid_y - orig_cell_pos[1] - 1) + cell_w, cell_h = self.cell_pixel_size() + agent_image = cv2.resize(spirit, (cell_w, cell_h), interpolation=cv2.INTER_AREA) + agent_offset_x, agent_offset_y = cell_pos[0] * cell_w, cell_pos[1] * cell_h + agent_end_x, agent_end_y = agent_offset_x + cell_w, agent_offset_y + cell_h + agent_img_gray = agent_image[:, :, 3] + _, mask = cv2.threshold(agent_img_gray, 120, 255, cv2.THRESH_BINARY) + mask_inv = cv2.bitwise_not(agent_img_gray) + source = background[agent_offset_y:agent_end_y, agent_offset_x:agent_end_x] + bg = cv2.bitwise_or(source, source, mask=mask_inv) + fg = cv2.bitwise_and(agent_image, agent_image, mask=mask) + background[agent_offset_y:agent_end_y, agent_offset_x:agent_end_x] = cv2.add(bg, fg[:, :, 0:3]) + + def paint_color(self, background, colors, cell_pos): + size_x, size_y = 10, 10 + cell_pos = (cell_pos[0], self.grid_y - cell_pos[1] - 1) + cell_w, cell_h = self.cell_pixel_size() + agent_offset_x, agent_offset_y = cell_pos[0] * cell_w, cell_pos[1] * cell_h + agent_end_x, agent_end_y = agent_offset_x + size_x, agent_offset_y + size_y + get_channel = lambda c: numpy.ones((size_y, size_x), dtype=numpy.uint8) * int(255 * colors[c]) + color = numpy.transpose(numpy.stack([get_channel(i) for i in range(3)]), (1, 2, 0)) + background[agent_offset_y:agent_end_y, agent_offset_x:agent_end_x] = color + + def print_state(self): + print("┌" + ("─" * ((self.grid_x + 2) * 2 - 3)) + "┐") + for j in range(self.grid_y - 1, -1, -1): + print("│", end=" ") + for i in range(self.grid_x): + print(self.pos_char((i, j)), end=" ") + print("│") + print("└" + ("─" * ((self.grid_x + 2) * 2 - 3)) + "┘") + + def pos_char(self, pos): + if pos == self.curr_pos: return 'C' + elif pos == self.start_pos: return 'S' + elif pos == self.goal_pos: return 'G' + elif pos in self.enemies: return '▒' + else: return ' ' + + def string_of_action(self, action): + if action == 0: return "up" + elif action == 1: return "right" + elif action == 2: return "down" + elif action == 3: return "left" + else: raise Exception(f"Unknown action `{action}`") + + def sample_point(self): + return (random.randint(0, self.grid_x - 1), random.randint(0, self.grid_y - 1)) + + def ok_enemy_position(self, pos): + return self.manhatten_distance(pos, self.start_pos) > 1 and self.manhatten_distance(pos, self.goal_pos) > 1 + + def cell_pixel_size(self): + return (int(self.cell_size * self.dpi), int(self.cell_size * self.dpi)) + + def manhatten_distance(self, p1, p2): + return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1]) + +def crop_cell_image(image, grid_dim, cell_pixel_size, orig_cell_pos): + cell_pos = (orig_cell_pos[0], grid_dim[1] - orig_cell_pos[1] - 1) + cell_w, cell_h = cell_pixel_size + agent_offset_x, agent_offset_y = cell_pos[0] * cell_w, cell_pos[1] * cell_h + agent_end_x, agent_end_y = agent_offset_x + cell_w, agent_offset_y + cell_h + return image[agent_offset_y:agent_end_y, agent_offset_x:agent_end_x] + +def crop_cell_image_torch(image, grid_dim, cell_pixel_size, orig_cell_pos): + cell_pos = (orig_cell_pos[0], grid_dim[1] - orig_cell_pos[1] - 1) + cell_w, cell_h = cell_pixel_size + agent_offset_x, agent_offset_y = cell_pos[0] * cell_w, cell_pos[1] * cell_h + agent_end_x, agent_end_y = agent_offset_x + cell_w, agent_offset_y + cell_h + return image[:, agent_offset_y:agent_end_y, agent_offset_x:agent_end_x] diff --git a/experiments/pacman_maze/examples/arena_1.scl b/experiments/pacman_maze/examples/arena_1.scl new file mode 100644 index 0000000..cc941bb --- /dev/null +++ b/experiments/pacman_maze/examples/arena_1.scl @@ -0,0 +1,25 @@ +import "../scl/arena.scl" +import "../scl/grid_node.scl" + +// This example replicates the following 4x4 grid +// +// * O * G +// * * X * +// * O O O +// * * * * + +rel grid_size(4, 4) + +rel curr_position = {0.98::(2, 2)} + +rel goal_position = {0.98::(3, 3)} + +rel is_enemy = { + 0.01::(0, 3), 0.99::(1, 3), 0.01::(2, 3), 0.01::(3, 3), + 0.01::(0, 2), 0.01::(1, 2), 0.01::(2, 2), 0.01::(3, 2), + 0.01::(0, 1), 0.99::(1, 1), 0.99::(2, 1), 0.99::(3, 1), + 0.01::(0, 0), 0.01::(1, 0), 0.01::(2, 0), 0.01::(3, 0), +} + +query action_score +query next_position diff --git a/experiments/pacman_maze/examples/arena_2.scl b/experiments/pacman_maze/examples/arena_2.scl new file mode 100644 index 0000000..2f8f32a --- /dev/null +++ b/experiments/pacman_maze/examples/arena_2.scl @@ -0,0 +1,25 @@ +import "../scl/arena.scl" +import "../scl/grid_node.scl" + +// This example replicates the following 4x4 grid +// +// * O * G +// * * * * +// * O O O +// * * X * + +rel grid_size(4, 4) + +rel curr_position = {0.98::(2, 0)} + +rel goal_position = {0.99::(3, 3)} + +rel is_enemy = { + 0.01::(0, 3), 0.99::(1, 3), 0.01::(2, 3), 0.01::(3, 3), + 0.01::(0, 2), 0.01::(1, 2), 0.01::(2, 2), 0.01::(3, 2), + 0.01::(0, 1), 0.99::(1, 1), 0.99::(2, 1), 0.99::(3, 1), + 0.01::(0, 0), 0.01::(1, 0), 0.01::(2, 0), 0.01::(3, 0), +} + +query action_score +query next_position diff --git a/experiments/pacman_maze/examples/arena_3.scl b/experiments/pacman_maze/examples/arena_3.scl new file mode 100644 index 0000000..48627ce --- /dev/null +++ b/experiments/pacman_maze/examples/arena_3.scl @@ -0,0 +1,26 @@ +import "../scl/arena.scl" +import "../scl/grid_node.scl" + +// This example replicates the following 4x4 grid +// +// * O * G +// * * * * +// * O O O +// * * X * + +rel grid_size(4, 4) + +rel curr_position = {0.98::(3, 2)} + +rel goal_position = {0.99::(3, 3)} + +rel is_enemy = { + 0.01::(0, 3), 0.99::(1, 3), 0.01::(2, 3), 0.01::(3, 3), + 0.01::(0, 2), 0.01::(1, 2), 0.01::(2, 2), 0.01::(3, 2), + 0.01::(0, 1), 0.99::(1, 1), 0.99::(2, 1), 0.99::(3, 1), + 0.01::(0, 0), 0.01::(1, 0), 0.01::(2, 0), 0.01::(3, 0), +} + +query action_score +// query next_position +// query node diff --git a/experiments/pacman_maze/examples/arena_4.scl b/experiments/pacman_maze/examples/arena_4.scl new file mode 100644 index 0000000..57ce773 --- /dev/null +++ b/experiments/pacman_maze/examples/arena_4.scl @@ -0,0 +1,31 @@ +import "../scl/arena.scl" +import "../scl/grid_node.scl" + +// This example replicates the following 4x4 grid +// +// * * C * * +// * E * E * +// * * * * * +// G * E * E +// * * * * * + +rel grid_node = { + 0.95::(0, 4), 0.95::(1, 4), 0.95::(2, 4), 0.95::(3, 4), 0.95::(4, 4), + 0.95::(0, 3), 0.95::(1, 3), 0.95::(2, 3), 0.95::(3, 3), 0.95::(4, 3), + 0.95::(0, 2), 0.95::(1, 2), 0.95::(2, 2), 0.95::(3, 2), 0.95::(4, 2), + 0.95::(0, 1), 0.95::(1, 1), 0.95::(2, 1), 0.95::(3, 1), 0.95::(4, 1), + 0.95::(0, 0), 0.95::(1, 0), 0.95::(2, 0), 0.95::(3, 0), 0.95::(4, 0), +} + +rel curr_position = {(2, 4)} + +rel goal_position = {(0, 1)} + +rel is_enemy = {(1, 3), (3, 3), (2, 1), (4, 1)} + +query action_score + +// R: {Pos(2), Pos(3), Pos(7), Pos(11), Pos(12), Pos(15), Pos(16)}, +// {Pos(2), Pos(3), Pos(7), Pos(10), Pos(11), Pos(12), Pos(15)} +// {Pos(0), Pos(1), Pos(2), Pos(3), Pos(5), Pos(10), Pos(15)} +// L: {Pos(0), Pos(1), Pos(2), Pos(5), Pos(10), Pos(15)} diff --git a/experiments/pacman_maze/res/agent.png b/experiments/pacman_maze/res/agent.png new file mode 100644 index 0000000000000000000000000000000000000000..54a098d9c8e0e0aad436e54aff9be5ec534b67fd GIT binary patch literal 6142 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 QLI3~&07*qoM6N<$g2zVpIsgCw literal 0 HcmV?d00001 diff --git a/experiments/pacman_maze/res/back.webp b/experiments/pacman_maze/res/back.webp new file mode 100644 index 0000000000000000000000000000000000000000..b466bc355a3c89decbc63a808e9180f7e1ffc5b6 GIT binary patch literal 165582 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

NUp}b!Gj#qg*ODH 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 literal 0 HcmV?d00001 diff --git a/experiments/pacman_maze/res/enemy1.webp b/experiments/pacman_maze/res/enemy1.webp new file mode 100644 index 0000000000000000000000000000000000000000..d76767ebffc532aff63364241446f19063bb16cb GIT binary patch literal 4508 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 MCn9WDjT9&U1B_pPS^xk5 literal 0 HcmV?d00001 diff --git a/experiments/pacman_maze/res/flag.png b/experiments/pacman_maze/res/flag.png new file mode 100644 index 0000000000000000000000000000000000000000..6616cf6185e9c6fffa59f83cfea4cc953d1574b2 GIT binary patch literal 5913 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 1 + rel too_many_enemy() = n := count(x, y: enemy(x, y)), n > 5 + rel violation() = too_many_goal() or too_many_enemy() + """, + provenance="difftopkproofs", + k=1, + facts={"grid_node": [(torch.tensor(args.attenuation, requires_grad=False), c) for c in self.cells]}, + input_mappings={"actor": self.cells, "goal": self.cells, "enemy": self.cells}, + retain_topk={"actor": 3, "goal": 3, "enemy": 7}, + output_mappings={"next_action": list(range(4)), "violation": ()}, + retain_graph=True) + + def forward(self, x): + actor, goal, enemy = self.extract_entity(x) + result = self.path_planner(actor=actor, goal=goal, enemy=enemy) + next_action = torch.softmax(result["next_action"], dim=1) + violation = result["violation"] * 0.2 + return next_action, violation + + def visualize(self, arena, raw_image, torch_image): + curr_position, goal_position, is_enemy = self.extract_entity(torch_image) + for (i, c) in enumerate(self.cells): + blue, green, red = curr_position[0, i], goal_position[0, i], is_enemy[0, i] + arena.paint_color(raw_image, (blue, green, red), c) + return + + +class ReplayMemory: + def __init__(self, capacity): + self.memory = deque([], maxlen=capacity) + + def push(self, transition): + self.memory.append(transition) + + def sample(self, batch_size): + return random.sample(self.memory, batch_size) + + def __len__(self): + return len(self.memory) + + +class DQN: + def __init__(self, grid_x, grid_y, cell_pixel_size): + self.policy_net = PolicyNet(grid_x, grid_y, cell_pixel_size) + + # Create another network + self.target_net = PolicyNet(grid_x, grid_y, cell_pixel_size) + self.target_net.load_state_dict(self.policy_net.state_dict()) + self.target_net.eval() + + # Store replay memory + self.memory = ReplayMemory(args.replay_memory_capacity) + + # Loss function and optimizer + self.criterion = nn.HuberLoss() + self.violation_loss = nn.SmoothL1Loss() + self.optimizer = optim.RMSprop(self.policy_net.parameters(), args.learning_rate) + + def predict_action(self, state_image): + action_scores, _ = self.policy_net(state_image) # [0.25, 0.24, 0.26, 0.25] + action = torch.argmax(action_scores, dim=1) # 2 + return action + + def observe_transition(self, transition): + self.memory.push(transition) + + def optimize_model(self): + if len(self.memory) < args.batch_size: return 0.0 + + # Pull out a batch and its relevant features + batch = self.memory.sample(args.batch_size) + non_final_mask = torch.tensor([transition.next_state != None for transition in batch], dtype=torch.bool) + non_final_next_states = torch.stack([transition.next_state[0] for transition in batch if transition.next_state is not None]) + action_batch = torch.stack([transition.action for transition in batch]) + state_batch = torch.stack([transition.state[0] for transition in batch]) + reward_batch = torch.stack([torch.tensor(transition.reward) for transition in batch]) + + # Prepare the loss function + state_action_values_raw, violations = self.policy_net(state_batch) + state_action_values = state_action_values_raw.gather(1, action_batch)[:, 0] + next_state_values = torch.zeros(args.batch_size) + next_state_values[non_final_mask] = self.target_net(non_final_next_states)[0].max(1)[0].detach() + expected_state_action_values = (next_state_values * args.gamma) + reward_batch + + # Compute the loss: + loss1 = self.criterion(state_action_values, expected_state_action_values) # loss1 is the criterion + loss2 = self.violation_loss(violations, torch.zeros(args.batch_size)) # loss2 is the violation + loss = loss1 + loss2 + + self.optimizer.zero_grad() + loss.backward(retain_graph=True) + for param in self.policy_net.parameters(): + param.grad.data.clamp_(-1, 1) + self.optimizer.step() + + # Return loss + return loss.detach() + + def update_target(self): + self.target_net.load_state_dict(self.policy_net.state_dict()) + + +class Trainer: + def __init__(self, grid_x, grid_y, cell_size, dpi, num_enemies, epsilon): + self.arena = AvoidingArena((grid_x, grid_y), cell_size, dpi, num_enemies, easy=args.easy) + self.dqn = DQN(grid_x, grid_y, self.arena.cell_pixel_size()) + self.epsilon = epsilon + + def show_image(self, raw_image, torch_image): + if args.overlay_prediction: + self.dqn.policy_net.visualize(self.arena, raw_image, torch_image) + cv2.namedWindow("Current Arena", cv2.WINDOW_NORMAL) + cv2.resizeWindow("Current Arena", args.window_size, args.window_size) + cv2.imshow("Current Arena", raw_image) + cv2.waitKey(int(args.show_run_interval * 1000)) + + def train_epoch(self, epoch): + self.epsilon = self.epsilon * args.epsilon_falloff + success, failure, optimize_count, sum_loss = 0, 0, 0, 0.0 + iterator = tqdm(range(args.num_train_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + curr_raw_image = self.arena.render() + curr_state_image = self.arena.render_torch_tensor(image=curr_raw_image) + for _ in range(args.num_steps): + # Render + if args.show_train_run: + self.show_image(curr_raw_image, curr_state_image) + + # Pick an action + if random.random() < self.epsilon: action = torch.tensor([random.randint(0, 3)]) + else: action = self.dqn.predict_action(curr_state_image) + + # Step the environment + _, done, reward, _ = self.arena.step(action[0]) + + # Get the next state + if done: + next_raw_image = None + next_state_image = None + else: + next_raw_image = self.arena.render() + next_state_image = self.arena.render_torch_tensor(image=next_raw_image) + + # Record the transition in memory buffer + transition = Transition(curr_state_image, action, next_state_image, reward) + self.dqn.observe_transition(transition) + + # Update the model + loss = self.dqn.optimize_model() + sum_loss += loss + optimize_count += 1 + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + else: + curr_raw_image = next_raw_image + curr_state_image = next_state_image + + # Update the target net + if episode_i % args.target_update == 0: + self.dqn.update_target() + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + avg_loss = sum_loss / optimize_count + iterator.set_description(f"[Train Epoch {epoch}] Avg Loss: {avg_loss}, Success: {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def test_epoch(self, epoch): + success, failure = 0, 0 + iterator = tqdm(range(args.num_test_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + for _ in range(args.num_steps): + # Show image + if args.show_test_run: + self.show_image(raw_image, state_image) + + # Pick an action + action = self.dqn.predict_action(state_image) + _, done, reward, _ = self.arena.step(action[0]) + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + iterator.set_description(f"[Test Epoch {epoch}] Success {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def run(self): + # self.test_epoch(0) + for i in range(1, args.num_epochs + 1): + self.train_epoch(i) + self.test_epoch(i) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--batch-size", type=int, default=24) + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--num-epochs", type=int, default=100) + parser.add_argument("--num-train-episodes", type=int, default=100) + parser.add_argument("--num-test-episodes", type=int, default=100) + parser.add_argument("--num-steps", type=int, default=30) + parser.add_argument("--target-update", type=int, default=10) + parser.add_argument("--epsilon", type=float, default=0.9) + parser.add_argument("--epsilon-falloff", type=float, default=0.98) + parser.add_argument("--gamma", type=float, default=0.999) + parser.add_argument("--learning-rate", type=float, default=0.0001) + parser.add_argument("--replay-memory-capacity", type=int, default=3000) + parser.add_argument("--seed", type=int, default=1357) + parser.add_argument("--cuda", action="store_true") + parser.add_argument("--gpu", type=int, default=0) + parser.add_argument("--attenuation", type=float, default=0.95) + parser.add_argument("--show-run", action="store_true") + parser.add_argument("--show-train-run", action="store_true") + parser.add_argument("--show-test-run", action="store_true") + parser.add_argument("--show-run-interval", type=int, default=0.001) + parser.add_argument("--window-size", type=int, default=200) + parser.add_argument("--overlay-prediction", action="store_true") + parser.add_argument("--easy", action="store_true") + args = parser.parse_args() + + # Set parameters + args.show_run_interval = max(0.001, args.show_run_interval) # Minimum 1ms + if args.show_run: + args.show_train_run = True + args.show_test_run = True + torch.manual_seed(args.seed) + random.seed(args.seed) + if args.cuda: + if torch.cuda.is_available(): device = torch.device(f"cuda:{args.gpu}") + else: raise Exception("No cuda available") + else: device = torch.device("cpu") + + # Train + trainer = Trainer(args.grid_x, args.grid_y, args.cell_size, args.dpi, args.num_enemies, args.epsilon) + trainer.run() diff --git a/experiments/pacman_maze/run_demo.py b/experiments/pacman_maze/run_demo.py new file mode 100644 index 0000000..5937ad9 --- /dev/null +++ b/experiments/pacman_maze/run_demo.py @@ -0,0 +1,93 @@ +import random +import argparse +import cv2 + +from arena import AvoidingArena + +ESC_KEY = 27 +LEFT_KEY = 63234 +UP_KEY = 63232 +DOWN_KEY = 63233 +RIGHT_KEY = 63235 + +def show_image(env: AvoidingArena): + # Render an image + image = env.render() + + # Show the image + cv2.imshow("Avoid Enemy", image) + + # Show the cells + if args.show_cells: + cells = [(i, j) for i in range(args.grid_x) for j in range(args.grid_y)] + for cell_pos in cells: + cell_image = env.get_cell_image(image, cell_pos) + cv2.imshow(f"Cell {cell_pos}", cell_image) + + # Wait for next frame + if args.auto: + cv2.waitKey(int(args.interval * 1000)) + elif args.manual: + key = cv2.waitKeyEx() + if key == ESC_KEY: + cv2.destroyAllWindows() + exit() + elif key == UP_KEY: + return env.step(0) + elif key == RIGHT_KEY: + return env.step(1) + elif key == DOWN_KEY: + return env.step(2) + elif key == LEFT_KEY: + return env.step(3) + else: + key = cv2.waitKey() + if key == ESC_KEY: + cv2.destroyAllWindows() + exit() + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--show-image", action="store_true") + parser.add_argument("--show-cells", action="store_true") + parser.add_argument("--print-arena", action="store_true") + parser.add_argument("--auto", action="store_true") + parser.add_argument("--manual", action="store_true") + parser.add_argument("--interval", type=float, default=0.3) + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--seed", type=int, default=1358) + args = parser.parse_args() + + # Manual + if args.manual: + args.show_image = True + random.seed(args.seed) + + # Start environment + env = AvoidingArena((args.grid_x, args.grid_y), args.cell_size, args.dpi, args.num_enemies) + done, reward = False, 0 + env.reset() + + # Print or show image + if args.print_arena: env.print_state() + if args.show_image: result = show_image(env) + + # Enter loop + while not done: + if not args.manual: + action = random.randint(0, 3) + print(f"Taking action: {env.string_of_action(action)}") + _, done, reward, _ = env.step(action) + else: + _, done, reward, _ = result + + # If finished + if done: print("Success!" if reward > 0 else "Failed!") + + # Print or show image + if args.print_arena: env.print_state() + if args.show_image: result = show_image(env) diff --git a/experiments/pacman_maze/run_dqn.py b/experiments/pacman_maze/run_dqn.py new file mode 100644 index 0000000..584806e --- /dev/null +++ b/experiments/pacman_maze/run_dqn.py @@ -0,0 +1,255 @@ +from argparse import ArgumentParser + +import random +import torch +from torch import nn +from torch import optim +import cv2 +from tqdm import tqdm +from collections import namedtuple, deque + +from arena import AvoidingArena + +Transition = namedtuple("Transition", ("state", "action", "next_state", "reward")) + +class PolicyNet(nn.Module): + def __init__(self): + super(PolicyNet, self).__init__() + self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=8, stride=4, padding=2) + self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=4, stride=4, padding=1) + self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=4, stride=4, padding=1) + self.fc1 = nn.Linear(in_features=576, out_features=256) + self.fc2 = nn.Linear(in_features=256, out_features=4) + self.relu = nn.ReLU() + + def forward(self, x): + batch_size, _, _, _ = x.shape + x = self.relu(self.conv1(x)) # In: (80, 80, 4) Out: (20, 20, 16) + x = self.relu(self.conv2(x)) # In: (20, 20, 16) Out: (10, 10, 32) + x = self.relu(self.conv3(x)) # In: (20, 20, 32) Out: (10, 10, 64) + x = x.view(batch_size, -1) # In: (10, 10, 32) Out: (3200,) + x = self.relu(self.fc1(x)) # In: (3200,) Out: (256,) + x = self.fc2(x) # In: (256,) Out: (4,) + return x + + +class ReplayMemory: + def __init__(self, capacity): + self.memory = deque([], maxlen=capacity) + + def push(self, transition): + self.memory.append(transition) + + def sample(self, batch_size): + return random.sample(self.memory, batch_size) + + def __len__(self): + return len(self.memory) + + +class DQN: + def __init__(self): + self.policy_net = PolicyNet() + + # Create another network + self.target_net = PolicyNet() + self.target_net.load_state_dict(self.policy_net.state_dict()) + self.target_net.eval() + + # Store replay memory + self.memory = ReplayMemory(args.replay_memory_capacity) + + # Loss function and optimizer + self.criterion = nn.SmoothL1Loss() + self.optimizer = optim.RMSprop(self.policy_net.parameters(), lr=args.learning_rate) + + def predict_action(self, state_image): + action_scores = self.policy_net(state_image) + action = torch.argmax(action_scores, dim=1) + return action + + def observe_transition(self, transition): + self.memory.push(transition) + + def optimize_model(self): + if len(self.memory) < args.batch_size: return 0.0 + + # Pull out a batch and its relevant features + batch = self.memory.sample(args.batch_size) + non_final_mask = torch.tensor([transition.next_state != None for transition in batch], dtype=torch.bool) + non_final_next_states = torch.stack([transition.next_state[0] for transition in batch if transition.next_state is not None]) + action_batch = torch.stack([transition.action for transition in batch]) + state_batch = torch.stack([transition.state[0] for transition in batch]) + reward_batch = torch.stack([torch.tensor(transition.reward) for transition in batch]) + + # Prepare the loss function + state_action_values = self.policy_net(state_batch).gather(1, action_batch)[:, 0] + next_state_values = torch.zeros(args.batch_size) + next_state_values[non_final_mask] = self.target_net(non_final_next_states).max(1)[0].detach() + expected_state_action_values = (next_state_values * args.gamma) + reward_batch + + # Compute the loss + loss = self.criterion(state_action_values, expected_state_action_values) + self.optimizer.zero_grad() + loss.backward() + for param in self.policy_net.parameters(): + param.grad.data.clamp_(-1, 1) + self.optimizer.step() + + # Return loss + return loss.detach() + + def update_target(self): + self.target_net.load_state_dict(self.policy_net.state_dict()) + + +class Trainer: + def __init__(self, grid_x, grid_y, cell_size, dpi, num_enemies, epsilon): + self.dqn = DQN() + self.epsilon = epsilon + self.arena = AvoidingArena( + (grid_x, grid_y), + cell_size, + dpi, + num_enemies, + default_reward=0.0, + on_success_reward=1.0, + on_failure_reward=0.0, + remain_unchanged_reward=0.0, + ) + + def show_image(self, image): + cv2.imshow("Current Arena", image) + cv2.waitKey(int(args.show_run_interval * 1000)) + + def train_epoch(self, epoch): + self.epsilon = self.epsilon * args.epsilon_falloff + success, failure, optimize_count, sum_loss = 0, 0, 0, 0.0 + iterator = tqdm(range(args.num_train_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + curr_raw_image = self.arena.render() + curr_state_image = self.arena.render_torch_tensor(image=curr_raw_image) + sum_loss = 0.0 + for _ in range(args.num_steps): + # Render + if args.show_train_run: + self.show_image(curr_raw_image) + + # Pick an action + if random.random() < self.epsilon: action = torch.tensor([random.randint(0, 3)]) + else: action = self.dqn.predict_action(curr_state_image) + + # Step the environment + _, done, reward, _ = self.arena.step(action[0]) + + # Record the transition in memory buffer + if done: + next_raw_image = None + next_state_image = None + else: + next_raw_image = self.arena.render() + next_state_image = self.arena.render_torch_tensor(image=next_raw_image) + transition = Transition(curr_state_image, action, next_state_image, reward) + self.dqn.observe_transition(transition) + + # Update the model + loss = self.dqn.optimize_model() + sum_loss += loss + optimize_count += 1 + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + else: + curr_raw_image = next_raw_image + curr_state_image = next_state_image + + # Update the target net + if episode_i % args.target_update == 0: + self.dqn.update_target() + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + avg_loss = sum_loss / optimize_count + iterator.set_description(f"[Train Epoch {epoch}] Avg Loss: {avg_loss}, Success: {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def test_epoch(self, epoch): + success, failure = 0, 0 + iterator = tqdm(range(args.num_test_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + for _ in range(args.num_steps): + # Show image + if args.show_test_run: + self.show_image(raw_image) + + # Pick an action + action = self.dqn.predict_action(state_image) + _, done, reward, _ = self.arena.step(action[0]) + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + iterator.set_description(f"[Test Epoch {epoch}] Success {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def run(self): + # self.test_epoch(0) + for i in range(1, args.num_epochs + 1): + self.train_epoch(i) + self.test_epoch(i) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--batch-size", type=int, default=16) + parser.add_argument("--panelize-staying", action="store_true") + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--num-epochs", type=int, default=100) + parser.add_argument("--num-train-episodes", type=int, default=500) + parser.add_argument("--num-test-episodes", type=int, default=50) + parser.add_argument("--num-steps", type=int, default=30) + parser.add_argument("--learning-rate", type=float, default=0.0001) + parser.add_argument("--target-update", type=int, default=10) + parser.add_argument("--epsilon", type=float, default=0.9) + parser.add_argument("--epsilon-falloff", type=float, default=0.98) + parser.add_argument("--gamma", type=float, default=0.999) + parser.add_argument("--replay-memory-capacity", type=int, default=1000) + parser.add_argument("--seed", type=int, default=1357) + parser.add_argument("--cuda", action="store_true") + parser.add_argument("--gpu", type=int, default=0) + parser.add_argument("--show-run", action="store_true") + parser.add_argument("--show-train-run", action="store_true") + parser.add_argument("--show-test-run", action="store_true") + parser.add_argument("--show-run-interval", type=int, default=0.001) + args = parser.parse_args() + + # Set parameters + torch.manual_seed(args.seed) + random.seed(args.seed) + if args.show_run: + args.show_train_run = True + args.show_test_run = True + if args.cuda: + if torch.cuda.is_available(): device = torch.device(f"cuda:{args.gpu}") + else: raise Exception("No cuda available") + else: device = torch.device("cpu") + + # Train + trainer = Trainer(args.grid_x, args.grid_y, args.cell_size, args.dpi, args.num_enemies, args.epsilon) + trainer.run() diff --git a/experiments/pacman_maze/run_old.py b/experiments/pacman_maze/run_old.py new file mode 100644 index 0000000..b51ca58 --- /dev/null +++ b/experiments/pacman_maze/run_old.py @@ -0,0 +1,322 @@ +from argparse import ArgumentParser +import random +import cv2 +import torch +from torch import nn +from torch import optim +from tqdm import tqdm +import scallopy +from collections import namedtuple, deque +import os + +from arena import AvoidingArena, crop_cell_image_torch + +FILE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "../")) + +Transition = namedtuple("Transition", ("state", "action", "next_state", "reward")) + +class CellClassifier(nn.Module): + """ + Classifies each cell (in image format) into one of 4 classes: agent, goal, enemy, [empty] + """ + def __init__(self): + super(CellClassifier, self).__init__() + self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=8, stride=4, padding=2) + self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=4, stride=4, padding=1) + self.fc1 = nn.Linear(in_features=288, out_features=256) + self.fc2 = nn.Linear(in_features=256, out_features=4) + self.relu = nn.ReLU() + + def forward(self, x): + batch_size, _, _, _ = x.shape + x = self.relu(self.conv1(x)) # In: (80, 80, 4) Out: (20, 20, 16) + x = self.relu(self.conv2(x)) # In: (20, 20, 16) Out: (10, 10, 32) + x = x.view(batch_size, -1) + x = self.relu(self.fc1(x)) # In: (3200,) Out: (256,) + x = self.fc2(x) # In: (256,) Out: (4,) + x = torch.softmax(x, dim=1) + return x + + +class EntityExtractor(nn.Module): + """ + Divide the whole image into grid cells, and pass the grid cells into the CellFeatureNet. + The output of this network is 3 separate vectors: is_agent, is_goal, is_enemy, + Each vector is of length #cells, mapping each cell to respective property (agent, goal, enemy) + """ + def __init__(self, grid_x, grid_y, cell_pixel_size): + super(EntityExtractor, self).__init__() + self.grid_x = grid_x + self.grid_y = grid_y + self.cell_pixel_size = cell_pixel_size + self.cell_dim = (self.grid_x, self.grid_y) + self.cells = [(i, j) for i in range(grid_x) for j in range(grid_y)] + self.cell_classifier = CellClassifier() + + def forward(self, x): + batch_size, _, _, _ = x.shape + num_cells = len(self.cells) + cells = torch.stack([torch.stack([crop_cell_image_torch(x[i], self.cell_dim, self.cell_pixel_size, c) for c in self.cells]) for i in range(batch_size)]) + cells = cells.reshape(batch_size * num_cells, 3, self.cell_pixel_size[0], self.cell_pixel_size[1]) + features = self.cell_classifier(cells) + batched_features = features.reshape(batch_size, num_cells, 4) + is_agent = batched_features[:, :, 0] + is_goal = batched_features[:, :, 1] + is_enemy = batched_features[:, :, 2] + return (is_agent, is_goal, is_enemy) + + +class PolicyNet(nn.Module): + """ + A policy net that takes in an image and return the action scores as [UP, RIGHT, BOTTOM, LEFT] + """ + def __init__(self, grid_x, grid_y, cell_pixel_size): + super(PolicyNet, self).__init__() + self.cells = [(x, y) for x in range(grid_x) for y in range(grid_y)] + self.grid_x = grid_x + self.grid_y = grid_y + + # Setup CNNs that process the image and extract features + self.extract_entity = EntityExtractor(grid_x, grid_y, cell_pixel_size) + + # Setup scallop context and scallop forward functions + self.ctx = scallopy.ScallopContext(provenance="difftopbottomkclauses", k=1) + self.ctx.import_file(os.path.join(FILE_DIR, "scl", "arena.scl")) + self.ctx.add_facts("grid_node", [(torch.tensor(args.attenuation, requires_grad=False), c) for c in self.cells]) + self.ctx.set_input_mapping("curr_position", self.cells, retain_k=3) + self.ctx.set_input_mapping("goal_position", self.cells, retain_k=3) + self.ctx.set_input_mapping("is_enemy", self.cells, retain_k=7) + self.predict_action = self.ctx.forward_function("action_score", list(range(4)), jit=args.jit, recompile=args.recompile) + + def forward(self, x): + curr_position, goal_position, is_enemy = self.extract_entity(x) + exp_reward = self.predict_action(curr_position=curr_position, goal_position=goal_position, is_enemy=is_enemy) + return exp_reward + + def visualize(self, arena, raw_image, torch_image): + curr_position, goal_position, is_enemy = self.extract_entity(torch_image) + for (i, c) in enumerate(self.cells): + blue, green, red = curr_position[0, i], goal_position[0, i], is_enemy[0, i] + arena.paint_color(raw_image, (blue, green, red), c) + return + + +class ReplayMemory: + def __init__(self, capacity): + self.memory = deque([], maxlen=capacity) + + def push(self, transition): + self.memory.append(transition) + + def sample(self, batch_size): + return random.sample(self.memory, batch_size) + + def __len__(self): + return len(self.memory) + + +class DQN: + def __init__(self, grid_x, grid_y, cell_pixel_size): + self.policy_net = PolicyNet(grid_x, grid_y, cell_pixel_size) + + # Create another network + self.target_net = PolicyNet(grid_x, grid_y, cell_pixel_size) + self.target_net.load_state_dict(self.policy_net.state_dict()) + self.target_net.eval() + + # Store replay memory + self.memory = ReplayMemory(args.replay_memory_capacity) + + # Loss function and optimizer + self.criterion = nn.HuberLoss() + self.optimizer = optim.RMSprop(self.policy_net.parameters(), args.learning_rate) + + def predict_action(self, state_image): + action_scores = self.policy_net(state_image) # [0.25, 0.24, 0.26, 0.25] + action = torch.argmax(action_scores, dim=1) # 2 + return action + + def observe_transition(self, transition): + self.memory.push(transition) + + def optimize_model(self): + if len(self.memory) < args.batch_size: return 0.0 + + # Pull out a batch and its relevant features + batch = self.memory.sample(args.batch_size) + non_final_mask = torch.tensor([transition.next_state != None for transition in batch], dtype=torch.bool) + non_final_next_states = torch.stack([transition.next_state[0] for transition in batch if transition.next_state is not None]) + action_batch = torch.stack([transition.action for transition in batch]) + state_batch = torch.stack([transition.state[0] for transition in batch]) + reward_batch = torch.stack([torch.tensor(transition.reward) for transition in batch]) + + # Prepare the loss function + state_action_values = self.policy_net(state_batch).gather(1, action_batch)[:, 0] + next_state_values = torch.zeros(args.batch_size) + next_state_values[non_final_mask] = self.target_net(non_final_next_states).max(1)[0].detach() + expected_state_action_values = (next_state_values * args.gamma) + reward_batch + + # Compute the loss + loss = self.criterion(state_action_values, expected_state_action_values) + self.optimizer.zero_grad() + loss.backward() + for param in self.policy_net.parameters(): + param.grad.data.clamp_(-1, 1) + self.optimizer.step() + + # Return loss + return loss.detach() + + def update_target(self): + self.target_net.load_state_dict(self.policy_net.state_dict()) + + +class Trainer: + def __init__(self, grid_x, grid_y, cell_size, dpi, num_enemies, epsilon): + self.arena = AvoidingArena((grid_x, grid_y), cell_size, dpi, num_enemies, easy=args.easy) + self.dqn = DQN(grid_x, grid_y, self.arena.cell_pixel_size()) + self.epsilon = epsilon + + def show_image(self, raw_image, torch_image): + if args.overlay_prediction: + self.dqn.policy_net.visualize(self.arena, raw_image, torch_image) + cv2.imshow("Current Arena", raw_image) + cv2.waitKey(int(args.show_run_interval * 1000)) + + def train_epoch(self, epoch): + self.epsilon = self.epsilon * args.epsilon_falloff + success, failure, optimize_count, sum_loss = 0, 0, 0, 0.0 + iterator = tqdm(range(args.num_train_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + curr_raw_image = self.arena.render() + curr_state_image = self.arena.render_torch_tensor(image=curr_raw_image) + for _ in range(args.num_steps): + # Render + if args.show_train_run: + self.show_image(curr_raw_image, curr_state_image) + + # Pick an action + if random.random() < self.epsilon: action = torch.tensor([random.randint(0, 3)]) + else: action = self.dqn.predict_action(curr_state_image) + + # Step the environment + _, done, reward, _ = self.arena.step(action[0]) + + # Get the next state + if done: + next_raw_image = None + next_state_image = None + else: + next_raw_image = self.arena.render() + next_state_image = self.arena.render_torch_tensor(image=next_raw_image) + + # Record the transition in memory buffer + transition = Transition(curr_state_image, action, next_state_image, reward) + self.dqn.observe_transition(transition) + + # Update the model + loss = self.dqn.optimize_model() + sum_loss += loss + optimize_count += 1 + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + else: + curr_raw_image = next_raw_image + curr_state_image = next_state_image + + # Update the target net + if episode_i % args.target_update == 0: + self.dqn.update_target() + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + avg_loss = sum_loss / optimize_count + iterator.set_description(f"[Train Epoch {epoch}] Avg Loss: {avg_loss}, Success: {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def test_epoch(self, epoch): + success, failure = 0, 0 + iterator = tqdm(range(args.num_test_episodes)) + for episode_i in iterator: + _ = self.arena.reset() + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + for _ in range(args.num_steps): + # Show image + if args.show_test_run: + self.show_image(raw_image, state_image) + + # Pick an action + action = self.dqn.predict_action(state_image) + _, done, reward, _ = self.arena.step(action[0]) + raw_image = self.arena.render() + state_image = self.arena.render_torch_tensor(image=raw_image) + + # Update the next state + if done: + if reward > 0: success += 1 + else: failure += 1 + break + + # Print information + success_rate = (success / (episode_i + 1)) * 100.0 + iterator.set_description(f"[Test Epoch {epoch}] Success {success}/{episode_i + 1} ({success_rate:.2f}%)") + + def run(self): + # self.test_epoch(0) + for i in range(1, args.num_epochs + 1): + self.train_epoch(i) + self.test_epoch(i) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--batch-size", type=int, default=24) + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--num-epochs", type=int, default=100) + parser.add_argument("--num-train-episodes", type=int, default=100) + parser.add_argument("--num-test-episodes", type=int, default=100) + parser.add_argument("--num-steps", type=int, default=30) + parser.add_argument("--target-update", type=int, default=10) + parser.add_argument("--epsilon", type=float, default=0.9) + parser.add_argument("--epsilon-falloff", type=float, default=0.98) + parser.add_argument("--gamma", type=float, default=0.999) + parser.add_argument("--learning-rate", type=float, default=0.0001) + parser.add_argument("--replay-memory-capacity", type=int, default=3000) + parser.add_argument("--seed", type=int, default=1357) + parser.add_argument("--cuda", action="store_true") + parser.add_argument("--gpu", type=int, default=0) + parser.add_argument("--jit", action="store_true") + parser.add_argument("--recompile", action="store_true") + parser.add_argument("--attenuation", type=float, default=0.95) + parser.add_argument("--show-run", action="store_true") + parser.add_argument("--show-train-run", action="store_true") + parser.add_argument("--show-test-run", action="store_true") + parser.add_argument("--show-run-interval", type=int, default=0.001) + parser.add_argument("--overlay-prediction", action="store_true") + parser.add_argument("--easy", action="store_true") + args = parser.parse_args() + + # Set parameters + args.show_run_interval = max(0.001, args.show_run_interval) # Minimum 1ms + if args.show_run: + args.show_train_run = True + args.show_test_run = True + torch.manual_seed(args.seed) + random.seed(args.seed) + if args.cuda: + if torch.cuda.is_available(): device = torch.device(f"cuda:{args.gpu}") + else: raise Exception("No cuda available") + else: device = torch.device("cpu") + + # Train + trainer = Trainer(args.grid_x, args.grid_y, args.cell_size, args.dpi, args.num_enemies, args.epsilon) + trainer.run() diff --git a/experiments/pacman_maze/run_random.py b/experiments/pacman_maze/run_random.py new file mode 100644 index 0000000..45d75b7 --- /dev/null +++ b/experiments/pacman_maze/run_random.py @@ -0,0 +1,45 @@ +from argparse import ArgumentParser +from tqdm import tqdm +import random + +from arena import AvoidingArena + +class RandomPolicy: + def __init__(self): pass + + def __call__(self, _): + return random.randint(0, 3) + +def test_random_model(): + # Initialize + arena = AvoidingArena((args.grid_x, args.grid_y), args.cell_size, args.dpi, args.num_enemies) + model = RandomPolicy() + success, failure = 0, 0 + iterator = tqdm(range(args.num_episodes)) + for episode_i in iterator: + state = arena.reset() + for _ in range(args.num_steps): + action = model(state) + _, done, reward, _ = arena.step(action) + if done: + if reward > 0: success += 1 + else: failure += 1 + break + + # Print + success_rate = (success / (episode_i + 1)) * 100.0 + iterator.set_description(f"[Test] {success}/{episode_i + 1} ({success_rate:.2f}%)") + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--num-episodes", type=int, default=1000) + parser.add_argument("--num-steps", type=int, default=30) + args = parser.parse_args() + + test_random_model() diff --git a/experiments/pacman_maze/run_scallop_sanity.py b/experiments/pacman_maze/run_scallop_sanity.py new file mode 100644 index 0000000..268ad23 --- /dev/null +++ b/experiments/pacman_maze/run_scallop_sanity.py @@ -0,0 +1,87 @@ +from argparse import ArgumentParser +from tqdm import tqdm +import numpy +import random +import scallopy +import os +import cv2 + +from arena import AvoidingArena + +FILE_DIR = os.path.abspath(os.path.join(os.path.abspath(__file__), "../")) + +class ScallopPolicy: + def __init__(self, grid_x, grid_y): + self.grid_x, self.grid_y = grid_x, grid_y + self.cells = [(x, y) for x in range(self.grid_x) for y in range(self.grid_y)] + self.ctx = scallopy.ScallopContext(provenance="topkproofs", k=1) + self.ctx.import_file(os.path.join(FILE_DIR, "scl", "arena.scl")) + self.ctx.add_facts("grid_node", [(args.attenuation, c) for c in self.cells]) + + def __call__(self, hidden_state): + curr_pos, goal_pos, enemies = hidden_state + temp_ctx = self.ctx.clone() + temp_ctx.add_facts("curr_position", [(None, curr_pos)]) + temp_ctx.add_facts("goal_position", [(None, goal_pos)]) + temp_ctx.add_facts("is_enemy", [(None, p) for p in enemies]) + temp_ctx.run() + output = list(temp_ctx.relation("action_score")) + if len(output) > 0: + action_id = numpy.argmax(numpy.array([p for (p, _) in output])) + action = output[action_id][1][0] + return action + else: + return random.randrange(0, 4) + + +def show_image(raw_image): + cv2.imshow("Current Arena", raw_image) + cv2.waitKey(int(args.show_run_interval * 1000)) + + +def test_scallop_model(): + # Initialize + arena = AvoidingArena((args.grid_x, args.grid_y), args.cell_size, args.dpi, args.num_enemies) + model = ScallopPolicy(args.grid_x, args.grid_y) + success, failure = 0, 0 + iterator = tqdm(range(args.num_episodes)) + for episode_i in iterator: + _ = arena.reset() + if args.show_run: + show_image(arena.render()) + for _ in range(args.num_steps): + action = model(arena.hidden_state()) + _, done, reward, _ = arena.step(action) + if args.show_run: + show_image(arena.render()) + if done: + if reward > 0: success += 1 + else: failure += 1 + break + + # Print + success_rate = (success / (episode_i + 1)) * 100.0 + iterator.set_description(f"[Test] {success}/{episode_i + 1} ({success_rate:.2f}%)") + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("--grid-x", type=int, default=5) + parser.add_argument("--grid-y", type=int, default=5) + parser.add_argument("--cell-size", type=float, default=0.5) + parser.add_argument("--dpi", type=int, default=80) + parser.add_argument("--num-enemies", type=int, default=5) + parser.add_argument("--num-episodes", type=int, default=1000) + parser.add_argument("--num-steps", type=int, default=30) + parser.add_argument("--attenuation", type=float, default=0.9) + parser.add_argument("--seed", type=int, default=12345) + parser.add_argument("--show-run", action="store_true") + parser.add_argument("--show-run-interval", type=float, default=0.001) + args = parser.parse_args() + + # Other arguments + args.show_run = True + random.seed(args.seed) + + # Test scallop model + test_scallop_model() diff --git a/experiments/pacman_maze/scl/arena.scl b/experiments/pacman_maze/scl/arena.scl new file mode 100644 index 0000000..0accd5f --- /dev/null +++ b/experiments/pacman_maze/scl/arena.scl @@ -0,0 +1,21 @@ +// Input from neural networks +type grid_node(x: usize, y: usize) +type curr_position(x: usize, y: usize) +type goal_position(x: usize, y: usize) +type is_enemy(x: usize, y: usize) + +// Basic connectivity +rel node(x, y) = grid_node(x, y), not is_enemy(x, y) +rel edge(x, y, x, yp, 0) = node(x, y), node(x, yp), yp == y + 1 // Up +rel edge(x, y, xp, y, 1) = node(x, y), node(xp, y), xp == x + 1 // Right +rel edge(x, y, x, yp, 2) = node(x, y), node(x, yp), yp == y - 1 // Down +rel edge(x, y, xp, y, 3) = node(x, y), node(xp, y), xp == x - 1 // Left + +// Path for connectivity; will condition on no enemy on the path +rel path(x, y, x, y) = node(x, y) +rel path(x, y, xp, yp) = edge(x, y, xp, yp, _) +rel path(x, y, xpp, ypp) = path(x, y, xp, yp), edge(xp, yp, xpp, ypp, _) + +// Get the next position +rel next_position(a, xp, yp) = curr_position(x, y), edge(x, y, xp, yp, a) +rel action_score(a) = next_position(a, x, y), goal_position(gx, gy), path(x, y, gx, gy) diff --git a/experiments/pacman_maze/scl/arena_w_constraint.scl b/experiments/pacman_maze/scl/arena_w_constraint.scl new file mode 100644 index 0000000..42e72b7 --- /dev/null +++ b/experiments/pacman_maze/scl/arena_w_constraint.scl @@ -0,0 +1,31 @@ +// Input from neural networks +type grid_node(x: usize, y: usize) +type curr_position(x: usize, y: usize) +type goal_position(x: usize, y: usize) +type is_enemy(x: usize, y: usize) + +// Basic connectivity +rel node(x, y) = grid_node(x, y), not is_enemy(x, y) +rel edge(x, y, x, yp, 0) = node(x, y), node(x, yp), yp == y + 1 // Up +rel edge(x, y, xp, y, 1) = node(x, y), node(xp, y), xp == x + 1 // Right +rel edge(x, y, x, yp, 2) = node(x, y), node(x, yp), yp == y - 1 // Down +rel edge(x, y, xp, y, 3) = node(x, y), node(xp, y), xp == x - 1 // Left + +// Path for connectivity; will condition on no enemy on the path +rel path(x, y, x, y) = node(x, y) +rel path(x, y, xp, yp) = edge(x, y, xp, yp, _) +rel path(x, y, xpp, ypp) = path(x, y, xp, yp), edge(xp, yp, xpp, ypp, _) + +// Get the next position +rel next_position(a, xp, yp) = curr_position(x, y), edge(x, y, xp, yp, a) +rel action_score(a) = next_position(a, x, y), goal_position(gx, gy), path(x, y, gx, gy) + +// Constraint violation +type too_many_goal() +type too_many_agent() +type too_many_enemy() +// Comment the following out if we don't want a particular constraint +rel too_many_goal() :- n = count(x, y: goal_position(x, y)), n > 1 +// rel too_many_agent() :- n = count(x, y: curr_position(x, y)), n > 1 +rel too_many_enemy() :- n = count(x, y: is_enemy(x, y)), n > 5 +rel violation() = too_many_goal() or too_many_enemy() or too_many_agent() diff --git a/experiments/pacman_maze/scl/grid_node.scl b/experiments/pacman_maze/scl/grid_node.scl new file mode 100644 index 0000000..379c5c4 --- /dev/null +++ b/experiments/pacman_maze/scl/grid_node.scl @@ -0,0 +1,5 @@ +type grid_size(x: usize, y: usize) + +rel grid_node(0, 0) +rel grid_node(x, yp) = grid_node(x, y), grid_size(_, gy), yp == y + 1, yp < gy +rel grid_node(xp, y) = grid_node(x, y), grid_size(gx, _), xp == x + 1, xp < gx