├── dyna_python_module ├── dyna.egg-info │ ├── dependency_links.txt │ ├── top_level.txt │ ├── requires.txt │ ├── PKG-INFO │ └── SOURCES.txt ├── dyna │ ├── start_dyna_repl.py │ ├── java_configure_jvm.py │ ├── dyna_ipython_magic.py │ ├── __init__.py │ └── java_wrapper.py ├── setup.py ├── test │ ├── test_wrapper.py │ ├── Notebook test.ipynb │ └── .ipynb_checkpoints │ │ └── Notebook test-checkpoint.ipynb └── README.md ├── test_programs ├── define_memo.dyna ├── imported_file2.dyna ├── fib.dyna ├── imported_file.dyna ├── import_file.dyna ├── parser_generator_test.dyna ├── edit_distance.dyna ├── basic1.dyna ├── quicksort.dyna ├── cky_parsing1.dyna ├── k_means.dyna ├── cky_parsing2.dyna └── simple_matrix.dyna ├── dyna ├── src ├── java │ └── dyna │ │ ├── RexprValueVariableForceExposed.java │ │ ├── DIteratorInstance.java │ │ ├── SimplePair.java │ │ ├── ExternalFunction.java │ │ ├── RexprValueVariable.java │ │ ├── DynaSyntaxError.java │ │ ├── OpaqueValue.java │ │ ├── DIterable.java │ │ ├── DynaMap.java │ │ ├── DynaUserError.java │ │ ├── IteratorBadBindingOrder.java │ │ ├── RexprValue.java │ │ ├── ContextHandle.java │ │ ├── IDynaAgendaWork.java │ │ ├── DynaJITRuntimeCheckFailed.java │ │ ├── Dynabase.java │ │ ├── MemoContainer.java │ │ ├── UnificationFailure.java │ │ ├── WebDemoMain.java │ │ ├── DIterator.java │ │ ├── DeferRewriteTillGround.java │ │ ├── DynaUserAssert.java │ │ ├── RContext.java │ │ ├── InvalidAssumption.java │ │ ├── ParserUtils.java │ │ ├── IPrefixTrie.java │ │ ├── Rexpr.java │ │ ├── ParserUnbufferedInputStream.java │ │ ├── TracedNumber.java │ │ ├── SimplifyRewriteCollection.java │ │ ├── StatusCounters.java │ │ ├── DynaMain.java │ │ ├── fastmap │ │ └── FastFlatMap.java │ │ ├── ThreadVar.java │ │ └── DynaAgenda.java ├── resources │ └── dyna │ │ ├── builtin_libraries │ │ ├── parser_generator.dyna │ │ ├── matrix.dyna │ │ ├── symbolic_auto_diff.dyna │ │ ├── state_transducer.dyna │ │ └── gradient_number.dyna │ │ ├── prelude_theory.dyna │ │ └── prelude.dyna └── clojure │ ├── notused_dyna │ ├── runtime_controller.clj │ ├── variable_assignment_context.clj │ ├── rexpr_combine_recursive.clj │ ├── rexpr_wrap_java.clj │ ├── static_key_map.clj │ └── gradient_numbers.clj │ └── dyna │ ├── rexpr_pretty_printer.clj │ ├── rexpr_constructors.clj │ ├── builtin_libraries │ └── parser_generator.clj │ ├── public_interface.clj │ ├── base_protocols.clj │ ├── core.clj │ ├── assumptions.clj │ ├── system.clj │ └── optimize_rexpr.clj ├── .gitignore ├── .github └── workflows │ └── run-tests.yml ├── README.md ├── test └── dyna │ ├── optimize_rexpr_test.clj │ ├── parser_test.clj │ ├── higher_order_test.clj │ ├── programs_test.clj │ ├── memoization_test.clj │ ├── efficient_rexpr_test.clj │ ├── benchmark_test.clj │ ├── jit_test_v1.clj │ ├── trie_test.clj │ └── core_test.clj ├── project.clj └── Makefile /dyna_python_module/dyna.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dyna_python_module/dyna.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | dyna 2 | -------------------------------------------------------------------------------- /test_programs/define_memo.dyna: -------------------------------------------------------------------------------- 1 | %$memo(&foo(+,-)) = "unk". -------------------------------------------------------------------------------- /dyna_python_module/dyna.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | JPype1>=1.3.0 2 | -------------------------------------------------------------------------------- /test_programs/imported_file2.dyna: -------------------------------------------------------------------------------- 1 | :- export auto_export/0. 2 | 3 | auto_export = "baz". 4 | -------------------------------------------------------------------------------- /dyna: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this runs the dyna repl 4 | 5 | make 6 | exec java -cp `make run-class-path` dyna.DynaMain "$@" 7 | -------------------------------------------------------------------------------- /src/java/dyna/RexprValueVariableForceExposed.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public interface RexprValueVariableForceExposed { 4 | } 5 | -------------------------------------------------------------------------------- /test_programs/fib.dyna: -------------------------------------------------------------------------------- 1 | :- memoize_unk fib/1. 2 | 3 | fib(0) := 0. 4 | fib(1) := 1. 5 | fib(X) := fib(X-1) + fib(X-2) for X > 1. 6 | 7 | 8 | assert fib(10) = 55. 9 | -------------------------------------------------------------------------------- /src/java/dyna/DIteratorInstance.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public interface DIteratorInstance { 4 | 5 | Object iter_variable_value(); 6 | 7 | DIterator iter_continuation(); 8 | } 9 | -------------------------------------------------------------------------------- /test_programs/imported_file.dyna: -------------------------------------------------------------------------------- 1 | 2 | foo(X) += X. 3 | foo(X) += 123. 4 | 5 | foo2(X) = X*2. 6 | 7 | baz = 456. 8 | 9 | idr = &indirect_call_me. 10 | 11 | indirect_call_me(X) = X + 7. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | target/ 4 | .lein-repl-history 5 | *.iml 6 | .nrepl-port 7 | .lein-failures 8 | .attach_pid* 9 | dyna-* 10 | .lein.sh 11 | .lein-failures* 12 | python_build/ 13 | -------------------------------------------------------------------------------- /src/java/dyna/SimplePair.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public final class SimplePair { 4 | public final A a; 5 | public final B b; 6 | public SimplePair(A a, B b) { this.a = a; this.b = b; } 7 | } 8 | -------------------------------------------------------------------------------- /src/resources/dyna/builtin_libraries/parser_generator.dyna: -------------------------------------------------------------------------------- 1 | 2 | :- load_clojure "/dyna/builtin_libraries/parser_generator". 3 | 4 | :- export parse/2. 5 | parse(Grammar, String) = '$$__instaparse_run_parser'('$$__instaparse_construct_parser'(Grammar), String). 6 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: lein tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: run tests 11 | run: | 12 | timeout 15m make github-test 13 | -------------------------------------------------------------------------------- /src/java/dyna/ExternalFunction.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * A function can be called from dyna as long as it implements this interface and is passed to DynaInterface/define_external_function 5 | */ 6 | public interface ExternalFunction { 7 | public Object call(Object... args); 8 | } 9 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/runtime_controller.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.runtime-controller 2 | (:require [dyna.utils :refer :all])) 3 | 4 | ;; might want to make the runtime controller as a Java interface s that it can be easily implemented by python methods 5 | (defsimpleinterface IDynaRuntimeController 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/java/dyna/RexprValueVariable.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * An interface which is specific to all things which should be treated like a 5 | * variable. The JIT will have its own variables which need to get handled specially 6 | */ 7 | public interface RexprValueVariable extends RexprValue { 8 | } 9 | -------------------------------------------------------------------------------- /dyna_python_module/dyna.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 2.1 2 | Name: dyna 3 | Version: 0.1.0 4 | Summary: Dyna programming language 5 | Home-page: http://dyna.org/ 6 | Author: Matthew Francis-Landau 7 | Author-email: matthew@matthewfl.com 8 | License: UNKNOWN 9 | Platform: UNKNOWN 10 | 11 | UNKNOWN 12 | 13 | -------------------------------------------------------------------------------- /src/java/dyna/DynaSyntaxError.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public class DynaSyntaxError extends DynaUserError { 4 | 5 | public DynaSyntaxError() {super("Syntax Error");} 6 | public DynaSyntaxError(String msg) { super(msg); } 7 | public DynaSyntaxError(String msg, Exception cause) { super(msg, cause); } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/java/dyna/OpaqueValue.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * This interface can be used by external systems to track that a value is 5 | * opaque to the dyna runtime. This interface is not used internally by Dyna 6 | * for anything and it is not checked, as such it is optional to even use this 7 | */ 8 | public interface OpaqueValue {} 9 | -------------------------------------------------------------------------------- /dyna_python_module/dyna.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | dyna/__init__.py 4 | dyna/dyna_ipython_magic.py 5 | dyna/java_configure_jvm.py 6 | dyna/java_wrapper.py 7 | dyna.egg-info/PKG-INFO 8 | dyna.egg-info/SOURCES.txt 9 | dyna.egg-info/dependency_links.txt 10 | dyna.egg-info/requires.txt 11 | dyna.egg-info/top_level.txt 12 | test/test_wrapper.py -------------------------------------------------------------------------------- /test_programs/import_file.dyna: -------------------------------------------------------------------------------- 1 | :- import foo/1,foo2/1 from "imported_file.dyna". 2 | :- from "imported_file" import baz/0, idr/0. 3 | 4 | 5 | :- import "imported_file2". % the name auto_export/0 is listed as an exported term in this file 6 | 7 | assert foo(3) = 126. 8 | 9 | assert baz = 456. 10 | 11 | assert auto_export = "baz". 12 | 13 | assert (idr)(10) = 17. -------------------------------------------------------------------------------- /dyna_python_module/dyna/start_dyna_repl.py: -------------------------------------------------------------------------------- 1 | import os, sys, importlib.resources 2 | 3 | 4 | def start_dyna_repl(): 5 | jar = importlib.resources.path('dyna', 'dyna.jar') 6 | if not os.path.exists(jar): 7 | print('Unable to find dyna runtime') 8 | sys.exit(1) 9 | os.execv(jar, sys.argv) 10 | 11 | if __name__ == '__main__': 12 | start_dyna_repl() 13 | -------------------------------------------------------------------------------- /test_programs/parser_generator_test.dyna: -------------------------------------------------------------------------------- 1 | :- import "parser_generator". 2 | 3 | grammar = '{ 4 | S = AB* 5 | AB = A B 6 | A = 'a'+ 7 | B = 'b'+ 8 | }. 9 | 10 | in = "aaaaabbbaaaabb". 11 | 12 | goal = &s(&ab(&a("a", "a", "a", "a", "a"), &b("b", "b", "b")), &ab(&a("a", "a", "a", "a"), &b("b", "b"))). 13 | 14 | out = parse(grammar, in). 15 | 16 | print out. 17 | 18 | assert out = goal. 19 | -------------------------------------------------------------------------------- /src/java/dyna/DIterable.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public interface DIterable { 4 | // return a set of which variables this iterator will bind 5 | Object iter_what_variables_bound(); 6 | 7 | // return a list of different valid binding orders 8 | // the list inside should be order. 9 | Object iter_variable_binding_order(); 10 | 11 | DIterator iter_create_iterator(Object which_binding); 12 | } 13 | -------------------------------------------------------------------------------- /src/java/dyna/DynaMap.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.RT; 4 | 5 | /** 6 | * A wrapper around a hash map 7 | */ 8 | final public class DynaMap { 9 | final public Object map_elements; 10 | 11 | public DynaMap(Object m) { 12 | map_elements = m; 13 | } 14 | 15 | public static DynaMap create(Object[] args) { 16 | return new DynaMap(RT.map(args)); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/java/dyna/DynaUserError.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | 4 | /** 5 | * base class for errors which are the result of the user doing something that they shouldn't have done 6 | */ 7 | public class DynaUserError extends RuntimeException { 8 | 9 | public DynaUserError() {} 10 | public DynaUserError(String msg) { super(msg); } 11 | public DynaUserError(String msg, Exception cause) { super(msg, cause); } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/java/dyna/IteratorBadBindingOrder.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * This exception would indicate some kind of error, but it could happen from 5 | * JITted code which is going to make assumptions about what it can do without 6 | * necessarily checking with all of the possible combinations first. 7 | */ 8 | public class IteratorBadBindingOrder extends RuntimeException { 9 | public IteratorBadBindingOrder(String msg) { 10 | super(msg); 11 | } 12 | 13 | public IteratorBadBindingOrder() { 14 | super("Bad binding order for iterator"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/java/dyna/RexprValue.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * This represents things which can have values. This is variables and constant values 5 | */ 6 | public interface RexprValue { 7 | 8 | Object get_value(); 9 | 10 | Object get_value_in_context(RContext ctx); 11 | 12 | RexprValue get_representation_in_context(RContext ctx); 13 | 14 | void set_value_BANG_(Object value); 15 | 16 | void set_value_in_context_BANG_(RContext ctx, Object value); 17 | 18 | boolean is_bound_QMARK_(); 19 | 20 | boolean is_bound_in_context_QMARK_(RContext ctx); 21 | 22 | Object all_variables(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/java/dyna/ContextHandle.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * Get the pointer to the current context used by the current thread. Bypass 5 | * using clojure's binding as that is going to indirect through its hashmap, 6 | * where using the java builtin threadlocal should only have a few indirections 7 | * through pointers/arrays. 8 | */ 9 | public class ContextHandle { 10 | private ContextHandle() {} 11 | 12 | public static final ThreadLocal handle = new ThreadLocal<>(); 13 | 14 | public static RContext get() { return handle.get(); } 15 | public static void set(RContext v) { handle.set(v); } 16 | } 17 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/variable_assignment_context.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.variable-assignment-context 2 | (:require [dyna.rexpr :refer :all])) 3 | 4 | 5 | (assert false) ;; not used 6 | 7 | ;; there should be some representation which allows for the different values 8 | ;; to get the contextual info 9 | 10 | (def-base-rexpr contextual-info [:unchecked contextual-bindings 11 | :rexpr R]) 12 | 13 | ;(def-rewrite 14 | ; :match (contextual-info (:unchecked LContext) (:rexpr R))) 15 | ; 16 | ; 17 | ; 18 | ; 19 | ;(comment 20 | ; (def-nesting-rewrite 21 | ; :match (contextual-info (:unchecked LContext) (:rexpr R)))) 22 | ; 23 | ; 24 | ; 25 | -------------------------------------------------------------------------------- /test_programs/edit_distance.dyna: -------------------------------------------------------------------------------- 1 | 2 | % Base case: distance between two empty strings. 3 | dist([],[]) min= 0. 4 | 5 | dist([X|Xs], Ys) min= del + dist(Xs,Ys). 6 | dist( Xs, [Y|Ys]) min= ins + dist(Xs,Ys). 7 | dist([X|Xs], [Y|Ys]) min= 0 + dist(Xs,Ys) for X==Y. 8 | dist([X|Xs], [Y|Ys]) min= subst + dist(Xs,Ys) for X!=Y. 9 | 10 | del := 1. 11 | ins := 1. 12 | 13 | subst := 1. 14 | 15 | assert dist(["a", "b"], ["s", "a", "b"]) = 1. 16 | 17 | % this is slow without haivng a dynamic program/forward chaining working yet 18 | % assert dist(["a","b","c","d"], ["s","b","c","t","d"]) = 2. 19 | 20 | %subst := 1.5. 21 | %assert dist(["a","b","c","d"], ["s","b","c","t","d"]) = 2.5. -------------------------------------------------------------------------------- /test_programs/basic1.dyna: -------------------------------------------------------------------------------- 1 | print "hello world". 2 | 3 | print X + 1. 4 | 5 | print X + 1 for X = 3. 6 | 7 | print '{ 8 | this is a longer message { 9 | that 10 | we are going } 11 | to test out 12 | 13 | }. 14 | 15 | 16 | my_f(X) = str("this is a concat string test ", X). 17 | print my_f("to see all of this together"). 18 | 19 | print my_f'{ 20 | to have a larger block of text here 21 | }. 22 | 23 | my_concat(A,B) = str("this concats two strings ", A, " and ", B). 24 | 25 | print my_concat("this is the first")'{ 26 | and this is the second % this comment should not print out 27 | 28 | }. 29 | 30 | 31 | z := 1. 32 | %assert z = 1. 33 | print z. 34 | -------------------------------------------------------------------------------- /test_programs/quicksort.dyna: -------------------------------------------------------------------------------- 1 | 2 | partition([], _, [], []). 3 | partition([X|Xs], Pivot, [X|S], B) :- 4 | partition(Xs, Pivot, S, B) for X < Pivot. 5 | partition([X|Xs], Pivot, S, [X|B]) :- 6 | partition(Xs, Pivot, S, B) for X >= Pivot. 7 | 8 | quicksort([]) := []. 9 | quicksort([X|Xs]) := 10 | partition(Xs, X, S, B), 11 | concat(quicksort(S), [X | quicksort(B)]). 12 | 13 | concat([], Y) := Y. 14 | concat(X, []) := X. 15 | concat([X|Xs], Y) := [X|concat(Xs, Y)]. 16 | 17 | %assert concat([1,2], [3,4]) = [1,2,3,4]. 18 | 19 | print quicksort([2,3,4,1]). 20 | 21 | r(X) := X. % done to make sure that the values do not backflow 22 | 23 | assert r(quicksort([2,3,4,1])) = [1,2,3,4]. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dyna v3 2 | 3 | Dyna v3 is the latest implementation of the [Dyna](http://dyna.org) programming 4 | language. 5 | 6 | # Trying it out 7 | ``` 8 | ./dyna 9 | ``` 10 | This will compile the dyna runtime and start the dyna REPL. 11 | 12 | Alternatively, an online demo can be found at https://dyna.org/dyna3-demo/ 13 | 14 | # Building 15 | ``` 16 | make 17 | ``` 18 | 19 | A recent version of java is required. Running `make` should automattically 20 | download all dependencies. The dyna runtime will be compiled into 21 | `./dyna-standalone-*.run`. 22 | 23 | ## Running dyna 24 | ``` 25 | make 26 | ./dyna-standalone-*.run 27 | ``` 28 | 29 | ## Running tests 30 | ``` 31 | rlwrap -a lein test 32 | ``` 33 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/rexpr_combine_recursive.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.rexpr-combine-recursive 2 | (:require [dyna.rexpr :refer :all])) 3 | 4 | ;; in the case of a recursive function, we should combine the rexprs together 5 | ;; and propagate any information such as structured terms and constants. This 6 | ;; will allow for osmething like `list(&int, X)` to be efficient as the `int` 7 | ;; part would get propagated as a constant. This would essentially become a 8 | ;; distinct type on the expression. 9 | 10 | 11 | 12 | ;; this is going to also want to check a recursive user call for base cases in 13 | ;; the case that a new expression is formed. This is essentially "type 14 | ;; checking" for us when it comes to recursive types. 15 | -------------------------------------------------------------------------------- /src/java/dyna/IDynaAgendaWork.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import java.util.Comparator; 4 | 5 | public interface IDynaAgendaWork extends Runnable { 6 | 7 | public void run(); 8 | 9 | public float priority(); 10 | 11 | public final static Comparator comparator = 12 | new Comparator () { 13 | public int compare(IDynaAgendaWork o1, IDynaAgendaWork o2) { 14 | float a = o1.priority(), b = o2.priority(); 15 | if(a == b) return 0; 16 | if(a > b) return -1; 17 | else return 1; 18 | } 19 | public boolean equals(IDynaAgendaWork o1, IDynaAgendaWork o2) { return o1 == o2; } 20 | }; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/java/dyna/DynaJITRuntimeCheckFailed.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * This exception is used internally in JITted code when some match expression 5 | * check fails. Because the JIT will have to fail and then fall back to some 6 | * seralization code, that can be handled using nested try/catch blocks where 7 | * this exception will be thrown. Given that all of the throw/catches of this 8 | * exception should be visible inside of the same block of code/compilation 9 | * unit, it shouldn't have much overhead 10 | */ 11 | public final class DynaJITRuntimeCheckFailed extends RuntimeException { 12 | public DynaJITRuntimeCheckFailed() { 13 | } 14 | 15 | @Override 16 | public Throwable fillInStackTrace() { 17 | return null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/java/dyna/Dynabase.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * Represent a handle for a dynabase 5 | */ 6 | final public class Dynabase { 7 | public final Object access_map; 8 | public Dynabase(Object m) { 9 | access_map = m; 10 | } 11 | public int hashCode() { 12 | return access_map == null ? 0 : access_map.hashCode(); 13 | } 14 | public boolean equals(Object o) { 15 | return (o instanceof Dynabase) && (access_map == null ? ((Dynabase)o).access_map == null : access_map.equals(((Dynabase)o).access_map)); 16 | } 17 | public String toString() { 18 | if(access_map != null) { 19 | return "(Dynabase " + access_map + ")"; 20 | } else { 21 | return "(Dynabase null)"; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/java/dyna/MemoContainer.java: -------------------------------------------------------------------------------- 1 | // package dyna; 2 | 3 | 4 | // /** 5 | // * This class holds the representation for a memo table 6 | // * the memo table must be updatable 7 | // */ 8 | // public class MemoContainer { 9 | // private Object Rconditional; 10 | // private Object Rmemo; 11 | // private Object Rorigional; 12 | // private Object assumption; 13 | 14 | // public MemoContainer(Object a, Object b, Object c, Object d) { 15 | // Rconditional = a; 16 | // Rmemo = b; 17 | // Rorigional = c; 18 | // assumption = d; 19 | // } 20 | 21 | // public Object get_memo() { 22 | // // this needs to track the assumption on the memo table 23 | // // the assumption should 24 | 25 | 26 | // } 27 | 28 | // // there needs to be some method which updates 29 | // } 30 | -------------------------------------------------------------------------------- /src/java/dyna/UnificationFailure.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | 4 | /** 5 | * A unification failure means an expresison like `1=2` was in the program. 6 | * This is equivalent to a `(multiplicity 0)` event. However, throwing a 7 | * unification failure can be more efficient than waiting for `(multiplicity 0)` 8 | * to bubble up the simplification stack. 9 | */ 10 | public class UnificationFailure extends RuntimeException { 11 | 12 | public UnificationFailure(String msg) { 13 | super(msg); 14 | } 15 | 16 | @Override 17 | public Throwable fillInStackTrace() { 18 | if(is_debugging) { 19 | return super.fillInStackTrace(); 20 | } else { 21 | return null; 22 | } 23 | } 24 | 25 | static final private boolean is_debugging = "true".equals(System.getProperty("dyna.debug", "true")); 26 | } 27 | -------------------------------------------------------------------------------- /dyna_python_module/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | 4 | setup(name='dyna', 5 | version='0.0.1', 6 | description='Dyna programming language', 7 | long_description="""# Dyna 8 | Dyna is a high level declaritive programming language. Dyna 9 | """, 10 | long_description_content_type='text/markdown', 11 | license='LGPLv3: GNU Lesser General Public License v3 https://www.gnu.org/licenses/lgpl-3.0.en.html', 12 | author='Matthew Francis-Landau', 13 | author_email='matthew@matthewfl.com', 14 | url='http://dyna.org/', 15 | packages=['dyna'], 16 | install_requires=[ 17 | 'JPype1>=1.3.0', # java wrapper for calling the JVM 18 | ], 19 | package_data = {'dyna': ['*.jar']}, 20 | entry_points = {'console_scripts': [ 21 | 'dyna = dyna.start_dyna_repl:start_dyna_repl', 22 | ]} 23 | ) 24 | -------------------------------------------------------------------------------- /src/java/dyna/WebDemoMain.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.java.api.Clojure; 4 | import clojure.lang.IFn; 5 | 6 | 7 | /** 8 | * Main class for running in the web browser. Will start the dyna runtime and receive 9 | * calls from javascript that manages the prompt from the user. 10 | */ 11 | public class WebDemoMain { 12 | 13 | private static IFn run_command; 14 | private static IFn validate_command; 15 | 16 | public static void load() { 17 | DynaMain.initRuntime(); 18 | run_command = (IFn)Clojure.var("dyna.repl", "web-repl-run-command"); 19 | validate_command = (IFn)Clojure.var("dyna.repl", "web-repl-validate-command"); 20 | } 21 | 22 | public static String validate(String command) { 23 | return (String)validate_command.invoke(command); 24 | } 25 | 26 | public static String run(String command) { 27 | return (String)run_command.invoke(command); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /test/dyna/optimize_rexpr_test.clj: -------------------------------------------------------------------------------- 1 | #_(ns dyna.optimize-rexpr-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.core]) 4 | (:require [dyna.base-protocols :refer :all]) 5 | (:require [dyna.rexpr-constructors :refer :all]) 6 | (:require [dyna.rexpr :refer [simplify-top]]) 7 | (:require [dyna.optimize-rexpr :refer [optimize-redudant-constraints]]) 8 | (:require [dyna.utils :refer :all]) 9 | (:require [dyna.simple-test :refer [str-test]])) 10 | 11 | #_(deftest redudant-constraints 12 | (let [rexpr (make-conjunct [(make-lessthan (make-variable 'X) (make-variable 'Y) (make-variable 'Z1)) 13 | (make-lessthan (make-variable 'X) (make-variable 'Y) (make-variable 'Z2))]) 14 | r2 (simplify-top rexpr) 15 | opt-r (optimize-redudant-constraints rexpr)] 16 | (is (= rexpr r2)) 17 | (is (not= rexpr opt-r)) 18 | (is (some is-unify? (map second (all-conjunctive-rexprs opt-r)))))) 19 | -------------------------------------------------------------------------------- /src/java/dyna/DIterator.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.IFn; 4 | import clojure.lang.Seqable; 5 | 6 | public interface DIterator { 7 | 8 | void iter_run_cb(IFn cb_function); 9 | 10 | // this should reutrn a (lazy) sequence of bindings for the variable 11 | Seqable iter_run_iterable(); 12 | 13 | // return a lazy sequence over bindings, but this might repeat a value, as 14 | // this is unconsolidated. This will allow us to to deal with the case that 15 | // there is an nil (wild card) binding in the prefix trie 16 | 17 | // the same branch might get returned multiple times also. So it is 18 | // ___NOT___ valid to simply sum the multiplicies of what is returned from 19 | // these branches. This methods should __ONLY__ be used to get an upper 20 | // bound on which disjuncts are possible 21 | Seqable iter_run_iterable_unconsolidated(); 22 | 23 | DIterator iter_bind_value(Object value); 24 | 25 | Object iter_debug_which_variable_bound(); 26 | 27 | int iter_estimate_cardinality(); 28 | } 29 | -------------------------------------------------------------------------------- /src/java/dyna/DeferRewriteTillGround.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * CURRENTLY NOT USED 5 | * 6 | * thinking that there are some things which might as well become deferred and 7 | * wait until they are ground. For example, in the case of a dynabase, there is 8 | * not really much that we can do if the dynabase itself is not a ground 9 | * variable. This would mean that we might attempt to save time by not 10 | * performing any rewrites against some group of expression until we have more information 11 | */ 12 | class DeferRewriteTillGround extends RuntimeException { 13 | 14 | public final RexprValueVariable variable; 15 | 16 | DeferRewriteTillGround(RexprValueVariable v) { 17 | super(); 18 | variable = v; 19 | } 20 | 21 | @Override 22 | public Throwable fillInStackTrace() { 23 | if(is_debugging) { 24 | return super.fillInStackTrace(); 25 | } else { 26 | return null; 27 | } 28 | } 29 | 30 | static final private boolean is_debugging = "true".equals(System.getProperty("dyna.debug", "true")); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/java/dyna/DynaUserAssert.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * Represent asserts which are from the user's program 5 | */ 6 | public class DynaUserAssert extends DynaUserError { 7 | 8 | public final Object filename; 9 | public final int line; 10 | public final String assert_text; 11 | public final Object assert_rexpr; 12 | 13 | public DynaUserAssert(Object filename, int line, String assert_text, Object assert_rexpr) { 14 | this.filename = filename; 15 | this.line = line; 16 | this.assert_text = assert_text; 17 | this.assert_rexpr = assert_rexpr; 18 | } 19 | 20 | public String getMessage() { 21 | if(filename == null) { 22 | return "Assert on line " + line + " failed\n==============================\n" + assert_text + "\n==============================\n"; 23 | } else { 24 | return "Assert at " + filename.toString() + ":"+line + " failed\n==============================\n" + assert_text + "\n==============================\n"; 25 | } 26 | } 27 | 28 | public String toString() { return getMessage(); } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/java/dyna/RContext.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.IFn; 4 | 5 | /** 6 | * Interface for the context passed around at runtime. This tracks conjunctive 7 | * R-exprs and bindings to variables. 8 | */ 9 | public interface RContext { 10 | 11 | Rexpr ctx_get_rexpr(); 12 | 13 | Object ctx_get_value(RexprValue variable); 14 | 15 | boolean ctx_is_bound_QMARK_(RexprValue variable); 16 | 17 | void ctx_set_value_BANG_(RexprValue variable, Object value); 18 | 19 | void ctx_add_rexpr_BANG_(Rexpr rexpr); 20 | 21 | void ctx_add_context_BANG_(RContext other_context); 22 | 23 | Object ctx_all_rexprs(); 24 | 25 | Object ctx_exit_context(Rexpr resulting_rexpr); 26 | 27 | Object ctx_intersect(RContext other); 28 | 29 | Object ctx_subtract(RContext other); 30 | 31 | Object ctx_scan_through_conjuncts(IFn scan_fn); 32 | 33 | boolean ctx_contains_rexpr_QMARK_(Rexpr rexpr); 34 | 35 | // for debugging 36 | Object ctx_get_inner_values(); 37 | Object ctx_get_all_bindings(); 38 | 39 | Object ctx_get_value_map_upto_context(RContext parent_context); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /dyna_python_module/dyna/java_configure_jvm.py: -------------------------------------------------------------------------------- 1 | import jpype as _jpype 2 | import os as _os 3 | import io as _io 4 | 5 | __all__ = [] 6 | 7 | def _configure_jvm(): 8 | import importlib.resources 9 | jar = importlib.resources.path('dyna', 'dyna.jar') 10 | if not _os.path.exists(jar): 11 | d = _os.path.join(_os.path.dirname(__file__), '../..') 12 | jar = _os.path.join(d, 'target/dyna-0.1.0-SNAPSHOT-standalone.jar') 13 | 14 | 15 | if not _os.path.exists(jar): 16 | #import ipdb; ipdb.set_trace() 17 | raise ImportError('Unable to find the dyna implmentation "dyna.jar"') 18 | 19 | # add the dyna jar to the classpath 20 | _jpype.addClassPath(jar) 21 | 22 | memory = _os.environ.get('DYNA_MEMORY', '2g') # the amount of memory that the backend should allocate 23 | 24 | # start the JVM and pass configuration flags 25 | _jpype.startJVM(None, *f'-Xmx{memory} -Dclojure.compiler.direct-linking=true -Ddyna.print_rewrites_performed=false -Ddyna.debug=false -Ddyna.trace_rexpr_construction=false -Ddyna.debug_repl=false'.split()) 26 | 27 | _jpype.JClass('dyna.DynaMain').backgroundInit() 28 | 29 | _configure_jvm() 30 | -------------------------------------------------------------------------------- /src/resources/dyna/builtin_libraries/matrix.dyna: -------------------------------------------------------------------------------- 1 | 2 | multiply(A,B) = matrix { 3 | elem(X,Y) += A.elem(X,Z) * B.elem(Z,Y). 4 | }. 5 | 6 | add(A, B) = matrix { 7 | elem(X,Y) += A.elem(X,Y) ; B.elem(X,Y). 8 | }. 9 | 10 | transpose(A) = matrix { 11 | elem(X,Y) = A.elem(Y,X). 12 | }. 13 | 14 | matrix(M) = { 15 | elem(X,Y) = M.elem(X,Y). 16 | transpose = transpose($self). 17 | add(Other) = add($self, Other). 18 | multiply(Other) = multiply($self,Other). 19 | 20 | row(X) :- _ = $self.elem(X, _). 21 | col(Y) :- _ = $self.elem(_, Y). 22 | rows += 1 for $self.row(_). 23 | cols += 1 for $self.col(_). 24 | }. 25 | 26 | zero_matrix(A,B) = matrix { 27 | elem(X:range(A), Y:range(B)) = 0. 28 | }. 29 | 30 | identity_matrix(I) = matrix { 31 | elem(A,A) += 1 for A:range(I). 32 | elem(A:range(I), B:range(I)) += 0. 33 | }. 34 | 35 | 36 | 37 | % parameter_matrix(Name) = matrix { 38 | % elem(X,Y) = $parameter(&matrix(Name,X,Y)). 39 | % }. 40 | 41 | %:- export parameter_matrix/1. 42 | 43 | 44 | 45 | :- export matrix/1. 46 | :- export zero_matrix/2. 47 | :- export identity_matrix/1. -------------------------------------------------------------------------------- /test_programs/cky_parsing1.dyna: -------------------------------------------------------------------------------- 1 | word(0) = "Papa". 2 | word(1) = "ate". 3 | word(2) = "the". 4 | word(3) = "caviar". 5 | word(4) = "with". 6 | word(5) = "a". 7 | word(6) = "spoon". 8 | word(7) = ".". 9 | 10 | 11 | rule("SD", "NP", "VP") = 1. 12 | rule("S", "SD", ".") = 1. 13 | rule("NP", "Det", "N") = 1. 14 | rule("NP", "NP", "PP") = 1. 15 | rule("VP", "V", "NP") = 1. 16 | rule("VP", "VP", "PP") = 1. 17 | rule("PP", "P", "NP") = 1. 18 | 19 | rule("NP", "Papa") = 1. 20 | rule("N", "caviar") = 1. 21 | rule("N", "spoon") = 1. 22 | rule("V", "ate") = 1. 23 | rule("P", "with") = 1. 24 | rule("Det", "the") = 1. 25 | rule("Det", "a") = 1. 26 | rule(".", ".") = 1. 27 | 28 | 29 | length max= I for _=word(I). 30 | 31 | phrase(I,K,T) max= phrase(I,J,T1) * phrase(J,K,T2) * rule(T,T1,T2) for range(I+1,K,J), I+2 <= K. 32 | phrase(I,I+1,T) max= rule(T, word(I)). 33 | 34 | $memo(phrase[A:$free,B:$free,C:$free]) = "null". 35 | 36 | print "program loaded, going to make query". 37 | 38 | % phrase(I:range(10),I+1,X)? 39 | 40 | phrase(A,B,C)? 41 | 42 | %phrase(0,2,X)? 43 | 44 | % debug_repl_clojure phrase(0,2,X). 45 | 46 | % phrase(0,1,X)? 47 | 48 | % phrase(A,B,C) ? 49 | 50 | % :- print_memo_table phrase/3. -------------------------------------------------------------------------------- /src/java/dyna/InvalidAssumption.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | /** 4 | * Assumptions are controlled in the file assumptions.clj. An assumption is 5 | * used to track anything which /might/ change in the future. An assumption can 6 | * only transition from a valid to invalid state. (A new assumption will be 7 | * created in its place, rather than having a reset mechnism). If an invalid 8 | * assumption is used for anything, then an invalid assumption exception will be 9 | * thrown. Depending on an invalid assumption is a waste of computation time, 10 | * hence we want to stop the computation as soon as we have identified that it 11 | * is invalid. 12 | */ 13 | public class InvalidAssumption extends RuntimeException { 14 | 15 | public InvalidAssumption(String msg) { 16 | super(msg); 17 | } 18 | 19 | @Override 20 | public Throwable fillInStackTrace() { 21 | // there should be something for checking if the debugger or asserts are enabled 22 | if(is_debugging) { 23 | return super.fillInStackTrace(); 24 | } else { 25 | return null; 26 | } 27 | } 28 | 29 | static final private boolean is_debugging = "true".equals(System.getProperty("dyna.debug", "true")); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /test/dyna/parser_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.parser-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.core]) 4 | (:require [dyna.ast-to-rexpr :refer [parse-string]])) 5 | 6 | 7 | (deftest basic-parser 8 | (parse-string "foo1 = 123.")) 9 | 10 | (deftest dynabase-test 11 | (parse-string "db_baz(X) = Y = 9, { something(W) = W*44. }.")) 12 | 13 | 14 | (deftest call-no-args 15 | (parse-string "a = matrix().")) 16 | 17 | (deftest bracket-pass 18 | (do 19 | (parse-string "a = m {}.") 20 | (parse-string "a = m { foo(X) = baz. }.") 21 | (parse-string "a = m { 123 -> 456, 789 -> 111, \"another\" -> \"what\" }."))) 22 | 23 | (deftest inline-aggregator 24 | (do 25 | (parse-string "foo(X) = (min= X;Y).") 26 | (parse-string "foo2(X,Y) = Func=((Z) min= X,Z ), Func(Y)."))) 27 | 28 | (deftest dynabase 29 | (parse-string "a = new {}.") 30 | (parse-string " 31 | a = new { 32 | f(X) = 123. 33 | }. 34 | 35 | v = {f = 123.}. 36 | 37 | b = { 38 | c = 3. 39 | }. 40 | 41 | f = new(b). 42 | 43 | z = foo { 44 | f = 123. 45 | }. 46 | 47 | w = f.a.b.c(123,456).e.w(9). 48 | ")) 49 | 50 | (deftest incomplete 51 | (try 52 | (do (parse-string "fib(0) += 0" :fragment-allowed false) 53 | (is false)) 54 | (catch RuntimeException err (is true)))) 55 | -------------------------------------------------------------------------------- /src/resources/dyna/builtin_libraries/symbolic_auto_diff.dyna: -------------------------------------------------------------------------------- 1 | :- export grad/1. 2 | :- export $grad/2. 3 | 4 | :- macro grad/1. 5 | 6 | rewrite_term(','[Term, Rest]) := 7 | rewrite_term(','[rewrite_terms(Term), rewrite_terms(Rest)]). 8 | 9 | selective_aggregator("max="). 10 | selective_aggregator("min="). 11 | selective_aggregator(":="). 12 | selective_aggregator("="). 13 | 14 | % the head like foo[X,Y,Z], the seconds is the dynabase addon to the term which should be nothing (atm), 15 | rewrite_term($define_term[TermHead, $nil, AggregatorName, Body]) := 16 | selective_aggregator(AggregatorName), 17 | UniqueId = str("gradient_body_", unique_id(*)), 18 | $reflect(InternalCall, UniqueId, [$quote1[TermHead]]), 19 | ','[$define_term[TermHead, $nil, AggregatorName, $dynabase_call[$variable["$self"], InternalCall]], 20 | ','[$define_term[InternalCall, $nil, AggregatorName, Body], 21 | $define_term[$grad[$quote1[TermHead]], $nil, "=", compute_grad(TermHead, Body)] 22 | ]]. 23 | 24 | compute_grad(TermHead, Body) := $error("not implemented"). 25 | 26 | 27 | 28 | grad(_) := $sytax_error("grad/1 expects a dynabase with no parent to perform symbolic differntation against"). 29 | grad($dynabase_create[$nil, Terms]) := 30 | $dynabase_create[$nil, rewrite_term(Terms)]. 31 | -------------------------------------------------------------------------------- /src/java/dyna/ParserUtils.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.java.api.Clojure; 4 | import clojure.lang.IFn; 5 | import clojure.lang.ILookup; 6 | import java.util.concurrent.atomic.AtomicLong; 7 | 8 | /** 9 | * Static method used by parser 10 | */ 11 | class ParserUtils { 12 | 13 | private ParserUtils() {} 14 | 15 | static private final IFn clojure_gensym; 16 | static private final IFn clojure_check_aggregator_defined; 17 | 18 | static { 19 | clojure_gensym = Clojure.var("clojure.core", "gensym"); 20 | clojure_check_aggregator_defined = Clojure.var("dyna.rexpr-aggregators", "is-aggregator-defined?"); 21 | } 22 | 23 | public static boolean aggregator_defined(String name) { 24 | Object result = clojure_check_aggregator_defined.invoke(name); 25 | return (Boolean)result; 26 | } 27 | 28 | public static String gensym_variable_name() { 29 | return clojure_gensym.invoke("$anon_var__").toString(); 30 | } 31 | 32 | private static final AtomicLong colon_line_counter_ = new AtomicLong(); 33 | public static long colon_line_counter() { 34 | // this returns the counter for the number of times that := rules have 35 | // appeared in the program 36 | return colon_line_counter_.getAndAdd(1); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/java/dyna/IPrefixTrie.java: -------------------------------------------------------------------------------- 1 | // package dyna; 2 | 3 | // import clojure.lang.Associative; 4 | 5 | // interface IPrefixTrie extends Associative { 6 | 7 | // // the arity of the object as presented 8 | // int arity(); 9 | 10 | // // the arity of the object without the additional filtered objects 11 | // int true_arity(); 12 | 13 | // // add to the the filter, the keys should match the arity 14 | // IPrefixTrie filter(Object keys); 15 | 16 | // // run over all of the values and apply the function. A new prefix trie is created and returned as a result 17 | // IPrefixTrie update_map_all(clojure.lang.IFn fn); 18 | 19 | // // join this trie with the selector argument. Run the fn with the value for 20 | // // this trie, the selector trie and then return a new trie which contains 21 | // // only the selected keys updated 22 | // IPrefixTrie update_map_subset(IPrefixTrie subset_selector, clojure.lang.IFn fn); 23 | 24 | // IPrefixTrie zip_tries(IPrefixTrie other); 25 | 26 | 27 | // IPrefixTrie assoc(Object key, Object val); 28 | 29 | // Object valAt(Object key);// { return valAt(key, null); } 30 | // Object valAt(Object key, Object notFound); 31 | 32 | // int count();// { throw RuntimeException("Not implemented"); } 33 | 34 | // IPrefixTrie cons(Object other); 35 | 36 | // boolean equiv(Object other); 37 | 38 | // } 39 | -------------------------------------------------------------------------------- /test_programs/k_means.dyna: -------------------------------------------------------------------------------- 1 | % This is a theoritical program for k-means where we are going to iterate with dyna 2 | 3 | num_centroids = 10. % define the number of k-means centroids to handle 4 | 5 | vector = { 6 | % element(X) = INPUT. 7 | 8 | distance(Other) = sqrt(distance_sum_squared(Other)). 9 | distance_sum_squared(Other) += (Other.element(X)-$self.element(X)) **2. % if both are defined, then the distance between the two elements 10 | distance_sum_squared(Other) += Other.element(X)**2 for !is_defined($self.element(X)). % if only one defines some element, then it will pretend that the other one has the value of zero 11 | distance_sum_squared(Other) += $self.element(X)**2 for !is_defined(Other.element(X)). 12 | 13 | size += 1 for _=element(_). 14 | }. 15 | 16 | data(Element) = new vector { 17 | element(X) = INPUT. % this would be replaced with the input somehow. This might work as an external call to python for example 18 | }. 19 | 20 | 21 | dimentions(X) :- data(_).element(X). 22 | 23 | centroids(Index:range(num_centroids), 0) = new vector { 24 | element(X) = random for dimentions(X). 25 | }. 26 | 27 | assigned_centroid(Element, T) = $arg((min= data(Element).distance(centroids(C, T)) arg C)). 28 | 29 | centroid_distance(C, Element, T) = data(Element).distance(centroids(C, T)). 30 | 31 | centroids(Index, T+1) = new vector { 32 | element(X) mean= data(Element).element(X) for assigned_centroid(Element,T)==Index. 33 | }. -------------------------------------------------------------------------------- /test_programs/cky_parsing2.dyna: -------------------------------------------------------------------------------- 1 | word(0) = "Papa". 2 | word(1) = "ate". 3 | word(2) = "the". 4 | word(3) = "caviar". 5 | word(4) = "with". 6 | word(5) = "a". 7 | word(6) = "spoon". 8 | word(7) = ".". 9 | 10 | 11 | % random numbers such that we will get a unique best parse 12 | rule("SD", "NP", "VP") = .2406. 13 | rule("S", "SD", ".") = .6518. 14 | rule("NP", "Det", "N") = .7946. 15 | rule("NP", "NP", "PP") = .1605. 16 | rule("VP", "V", "NP") = .9918. 17 | rule("VP", "VP", "PP") = .552. 18 | rule("PP", "P", "NP") = .457. 19 | 20 | rule("NP", "Papa") = 1. 21 | rule("N", "caviar") = 1. 22 | rule("N", "spoon") = 1. 23 | rule("V", "ate") = 1. 24 | rule("P", "with") = 1. 25 | rule("Det", "the") = 1. 26 | rule("Det", "a") = 1. 27 | rule(".", ".") = 1. 28 | 29 | 30 | length max= I for _=word(I). 31 | 32 | phrase(I,K,T) max= phrase(I,J,T1) * phrase(J,K,T2) * rule(T,T1,T2) for range(I+1,K,J), I+2 <= K arg x[T,$arg(phrase(I,J,T1)),$arg(phrase(J,K,T2))]. 33 | phrase(I,I+1,T) max= rule(T, word(I)) arg [word(I)]. 34 | 35 | $memo(phrase[A:$free,B:$free,C:$free]) = "null". 36 | 37 | print "program loaded, going to make query". 38 | 39 | phrase(A,B,C)? 40 | 41 | % max_non_term(A,B) max= phrase(A,B,X) arg X. 42 | % $arg(max_non_term(A,B)) ? 43 | 44 | print "parse". 45 | $arg(phrase(0,length+1,"S"))? 46 | 47 | target = x["S", x["SD", ["Papa"], x["VP", x["VP", ["ate"], x["NP", ["the"], ["caviar"]]], x["PP", ["with"], x["NP", ["a"], ["spoon"]]]]], ["."]]. 48 | 49 | assert target == $arg(phrase(0,length+1,"S")). -------------------------------------------------------------------------------- /test_programs/simple_matrix.dyna: -------------------------------------------------------------------------------- 1 | 2 | multiply(A,B) = matrix { 3 | elem(X,Y) += A.elem(X,Z) * B.elem(Z,Y). 4 | }. 5 | 6 | add(A, B) = matrix { 7 | elem(X,Y) += A.elem(X,Y) ; B.elem(X,Y). 8 | }. 9 | 10 | transpose(A) = matrix { 11 | elem(X,Y) = A.elem(Y,X). 12 | }. 13 | 14 | 15 | matrix(M) = { 16 | elem(X,Y) = M.elem(X,Y). 17 | transpose = transpose($self). 18 | transpose2 = matrix { elem(X,Y) = Owner.elem(Y,X). } for Owner=$self. 19 | add(Other) = add($self, Other). 20 | multiply(Other) = multiply($self,Other). 21 | 22 | rows max= 0. 23 | cols max= 0. 24 | rows max= X+1 for _ is $self.elem(X,_). 25 | cols max= Y+1 for _ is $self.elem(_,Y). 26 | 27 | row(X) :- _ = $self.elem(X, _). 28 | rows2 += 0. 29 | rows2 += 1 for $self.row(X). 30 | }. 31 | 32 | zero_matrix(A,B) = matrix { 33 | elem(X: range(0, A), Y: range(0,B)) = 0. 34 | 35 | %elem(X, Y) = 0 for range(0, A, X), range(0, B, Y). 36 | }. 37 | 38 | identity_matrix = matrix { 39 | elem(X,X) += 1. 40 | }. 41 | 42 | print "running matrix test". 43 | 44 | a = matrix { 45 | elem(0,0) = 1. 46 | elem(0,1) = 2. 47 | elem(1,1) = 1. 48 | elem(1,0) = 0. 49 | }. 50 | 51 | print "part 1". 52 | 53 | assert a.elem(0,0) = 1. 54 | assert a.transpose.elem(1,0) = 2. 55 | assert a.transpose2.elem(1,0) = 2. 56 | 57 | print "part 2". 58 | 59 | assert a.rows = 2. 60 | assert a.rows2 = 2. 61 | 62 | print "part 3". 63 | 64 | b = a.multiply(a.transpose). 65 | c = a.add(a). 66 | 67 | print "part 4". 68 | 69 | assert b.elem(0,0) = 5. 70 | 71 | print "part 5". 72 | 73 | assert c.elem(0,1) = 4. 74 | 75 | print "done". -------------------------------------------------------------------------------- /src/java/dyna/Rexpr.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.ILookup; // this is basically things like hash maps etc 4 | import clojure.lang.IFn; 5 | 6 | /** 7 | * The base interface for the R-expr 8 | */ 9 | public interface Rexpr { 10 | Rexpr primitive_rexpr(); 11 | 12 | //Rexpr primitive_rexpr_jit_placeholder(); 13 | 14 | Object get_variables(); 15 | 16 | Object get_children(); 17 | 18 | Object get_argument(int n); 19 | 20 | Object get_argument_name(Object name); 21 | 22 | Object get_arguments(); 23 | 24 | Object as_list(); 25 | 26 | Object exposed_variables(); 27 | 28 | Object get_all_variables_rec(); 29 | 30 | Rexpr remap_variables(ILookup renaming_map); 31 | 32 | Rexpr rewrite_rexpr_children(IFn remap_function); 33 | 34 | Rexpr rewrite_rexpr_children_no_simp(IFn remap_function); 35 | 36 | Rexpr remap_variables_func(IFn remap_function); 37 | 38 | Rexpr remap_variables_handle_hidden(ILookup renaming_map); 39 | 40 | Object rewrite_all_args(IFn remap_function); 41 | 42 | boolean is_constraint_QMARK_(); 43 | 44 | Object variable_functional_dependencies(); 45 | 46 | Object rexpr_name(); 47 | 48 | boolean is_empty_rexpr_QMARK_(); 49 | 50 | boolean is_non_empty_rexpr_QMARK_(); 51 | 52 | Object rexpr_map_function_with_access_path(IFn f); 53 | 54 | Object all_conjunctive_rexprs(); 55 | 56 | Object rexpr_jit_info(); 57 | 58 | int rexpr_jittype_hash(); 59 | 60 | Object check_rexpr_basecases(Object stack); 61 | 62 | SimplifyRewriteCollection rexpr_simplify_fast_collection(); 63 | 64 | SimplifyRewriteCollection rexpr_simplify_construct_collection(); 65 | 66 | SimplifyRewriteCollection rexpr_simplify_inference_collection(); 67 | 68 | SimplifyRewriteCollection rexpr_jit_compilation_step_collection(); 69 | } 70 | -------------------------------------------------------------------------------- /src/java/dyna/ParserUnbufferedInputStream.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import org.antlr.v4.runtime.UnbufferedCharStream; 4 | import org.antlr.v4.runtime.misc.Interval; 5 | 6 | import java.io.InputStream; 7 | 8 | /** 9 | * Circular buffer for the parser. Allows for bigger input files to be handled 10 | * rather than having to buffer everything in ram. 11 | */ 12 | public class ParserUnbufferedInputStream extends UnbufferedCharStream { 13 | 14 | public ParserUnbufferedInputStream(InputStream input, int bufferSize) { 15 | super(input, bufferSize); 16 | } 17 | 18 | private final int[] oldBuffer = new int[bufferSize]; 19 | 20 | @Override 21 | public void consume() { 22 | oldBuffer[currentCharIndex % oldBuffer.length] = data[p]; 23 | super.consume(); 24 | } 25 | 26 | @Override 27 | public String getText(final Interval interval) { 28 | final int startToken = interval.a; 29 | final int stopToken = interval.b; 30 | 31 | if(startToken < currentCharIndex - oldBuffer.length || startToken < 0) { 32 | throw new UnsupportedOperationException("interval " + interval + " outside buffer: " + startToken + ".." + (startToken + this.n - 1)); 33 | } 34 | 35 | int cpy[] = new int[stopToken - startToken + 1]; 36 | int idx = 0; 37 | for(int i = startToken; i <= stopToken; i++) { 38 | if(i < currentCharIndex) 39 | cpy[idx++] = oldBuffer[i % oldBuffer.length]; 40 | else 41 | cpy[idx++] = data[i - currentCharIndex + p]; 42 | } 43 | 44 | String ret = new String(cpy, 0, cpy.length); 45 | return ret; 46 | } 47 | 48 | @Override 49 | public int size() { 50 | return currentCharIndex; 51 | } 52 | 53 | static final int bufferSize = Integer.valueOf(System.getProperty("dyna.parser.bufferSize", "512000")); 54 | } 55 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/rexpr_wrap_java.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.rexpr-wrap-java 2 | (:require [dyna.rexpr :refer :all]) 3 | ) 4 | 5 | (assert false) ;; this is not used the $clojure builtin could be used instead 6 | ;; when calling something from java? Though might still be nicer if there was 7 | ;; something specific to java? 8 | 9 | ;; would be nice if there were some operations which wrapped java operations. 10 | ;; That way there could be some ability to have callouts to matrix libraries or 11 | ;; other useful libraries. One issue is that dyna is potentially running things 12 | ;; out of order, and something which gets its arguments read is going to try and 13 | ;; run. So how will this 14 | 15 | (defrecord DynaWrappedJavaObject [obj]) 16 | 17 | 18 | (def-base-rexpr java-new-instance [:var out 19 | :var string-class-name 20 | :var arguments]) 21 | 22 | (def-base-rexpr java-call-method [:var out 23 | :var method-name 24 | :var this-object 25 | :var arguments]) 26 | 27 | ;; if the type of some expression is known, then it does not have to use reflection to perform the call, so it would be better with it handling some 28 | ;; of the different expressions. 29 | 30 | ;; (def-base-rexpr java-call-type [:out]) 31 | 32 | 33 | ;; would be nice if there was some way of passing around opaque python objects 34 | ;; in the dyna runtime also. That would be nice for interface with python, 35 | ;; though that makes things a bit more annowing 36 | 37 | ;; if there are some expressions which correspond with 38 | 39 | 40 | 41 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 42 | 43 | ;; could do something like a `$clojure_eval(A,B,C)'{ (+ A B C) }` 44 | ;; Where A, B, C would become bindings to the variables. The result of this expression would 45 | -------------------------------------------------------------------------------- /src/clojure/dyna/rexpr_pretty_printer.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.rexpr-pretty-printer 2 | (:require [dyna.utils :refer :all]) 3 | (:require [dyna.base-protocols :refer :all])) 4 | 5 | (def ^{:dynamic true} *print-variable-name* (fn [var] 6 | (:varname var))) 7 | 8 | ;; symbols that we will define and then handle ourselves when formatting the string 9 | (defn optional-line-break [] 10 | "\ufff0") 11 | (defn indent-increase [] 12 | "\ufff1") 13 | (defn indent-decrease [] 14 | "\ufff2") 15 | 16 | ;; the these are otiemes/oplus, but these symbols are hard to read in the terminal. currently just using */+ but that is going to get confusing.... 17 | (defn symbol-conjunct [] 18 | "\u2a02") 19 | 20 | (defn symbol-disjunct [] 21 | "\u2a01") 22 | 23 | (defn- format-rexpr-string [^StringBuffer out target-width rexpr-str-seq] 24 | (let [current-line-length (volatile! 0) 25 | indent-stack (volatile! (list 0))] 26 | (loop [s rexpr-str-seq] 27 | (when-not (empty? s) 28 | (let [c (first s)] 29 | (case c 30 | \ufff0 (when (>= @current-line-length target-width) 31 | (.append out "\n") 32 | (.append out (apply str (repeat (first @indent-stack) " "))) 33 | (vreset! current-line-length (first @indent-stack))) 34 | \ufff1 (vswap! indent-stack #(cons @current-line-length %)) 35 | \ufff2 (vswap! indent-stack rest) 36 | (do 37 | (.append out c) 38 | (vswap! current-line-length inc))) 39 | (recur (rest s))))))) 40 | 41 | 42 | (defmulti rexpr-printer type) 43 | 44 | (defmethod rexpr-printer :default [rexpr] 45 | ;(debug-repl "default printer") 46 | (str rexpr)) 47 | 48 | (defn print-rexpr [rexpr & {:keys [width] :or {width 100}}] 49 | (let [s (rexpr-printer rexpr) 50 | sb (StringBuffer.)] 51 | (format-rexpr-string sb width (seq s)) 52 | (.toString sb))) 53 | -------------------------------------------------------------------------------- /src/clojure/dyna/rexpr_constructors.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.rexpr-constructors) 2 | 3 | (defmacro ^{:private true} declare-rexprs [& x] 4 | `(declare ~@(for [z x] (symbol (str "make-" z))) 5 | ~@(for [z x] (symbol (str "make-no-simp-" z))) 6 | ~@(for [z x] (symbol (str "is-" z "?"))) 7 | ~@(for [z x] (symbol (str "simplify-" z))) 8 | ~@(for [z x] (symbol (str "simplify-construct-" z))) 9 | ~@(for [z x] (symbol (str "simplify-inference-" z))) 10 | )) 11 | 12 | ;; the implementation is in rexpr.clj. This file just exists so that we can 13 | ;; reference the methods which construct the R-expr before clojure has loaded in 14 | ;; the impementation of the methods 15 | 16 | (declare-rexprs 17 | multiplicity 18 | conjunct 19 | disjunct 20 | unify 21 | unify-structure 22 | ;unify-structure-get-meta 23 | proj 24 | aggregator 25 | if 26 | user-call) 27 | 28 | (declare make-variable 29 | make-unique-variable 30 | is-variable? 31 | ;is-variable-set? 32 | make-constant 33 | is-constant? 34 | make-structure 35 | ; is-empty-rexpr? 36 | ; is-non-empty-rexpr? 37 | get-user-term 38 | ) 39 | 40 | (declare 41 | convert-to-jitted-rexpr) 42 | 43 | 44 | (def modification-lock (Object.)) 45 | 46 | (defmacro expose-globally 47 | ;; this namespace basically contains references to variables which need to be access from other files 48 | ;; this means that some function contained in this file might have multiple variables which reference it.... 49 | ([name] 50 | `(do (intern 'dyna.rexpr-constructors (quote ~(symbol name)) ~name) 51 | (alter-meta! (var ~name) assoc :all-vars #{(var ~(symbol "dyna.rexpr-constructors" (str name))) 52 | (var ~name)}) 53 | (alter-meta! (var ~(symbol "dyna.rexpr-constructors" (str name))) 54 | merge (dissoc (meta (var ~name)) :ns))))) 55 | -------------------------------------------------------------------------------- /dyna_python_module/dyna/dyna_ipython_magic.py: -------------------------------------------------------------------------------- 1 | import html 2 | from IPython.core.magic import register_cell_magic, needs_local_scope 3 | 4 | from dyna import Dyna as _Dyna_runtime 5 | 6 | class _HTML_results(list): 7 | 8 | def __init__(self, a): 9 | super().__init__(a) 10 | 11 | def _repr_html_(self): 12 | r = [''] 13 | for x in self: 14 | r.append('') 17 | r.append('
') 15 | r.append(html.escape(repr(x))) 16 | r.append('
') 18 | return ''.join(r) 19 | 20 | 21 | class _Error: 22 | 23 | def __init__(self, msg): 24 | self._message = msg 25 | 26 | def __str__(self): 27 | return f'ERROR: {self._message}' 28 | 29 | def __repr__(self): 30 | return f'ERROR: {self._message}' 31 | 32 | def _repr_html_(self): 33 | return ''+self._message.replace('\n', '
')+'
' 34 | 35 | 36 | @register_cell_magic 37 | @needs_local_scope 38 | def dyna(line, cell, local_ns): 39 | dyna_runtimes = set(x for x in local_ns.values() if isinstance(x, _Dyna_runtime)) 40 | if len(dyna_runtimes) == 0: 41 | return _Error('first create a dyna runtime using something like `from dyna import Dyna; dyna = Dyna()`') 42 | elif len(dyna_runtimes) > 1: 43 | return _Error('More than 1 dyna runtime in scope, unable to automattically figure out which one to use') 44 | 45 | # wonder if should make `#` work as a comment for the ipython cells. This 46 | # is not something that is currently in dyna, but it might make the code 47 | # look more python like when writing it in a notebook. The problem with 48 | # that would be that it would be inconsistent in different places.... 49 | 50 | r = next(iter(dyna_runtimes)) 51 | result = r.query(cell or line) # can this throw an exception? 52 | 53 | if len(result) == 0: 54 | return None 55 | elif len(result) == 1: 56 | return result[0] 57 | else: 58 | return _HTML_results(result) 59 | -------------------------------------------------------------------------------- /test/dyna/higher_order_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.higher-order-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.core]) 4 | (:require [dyna.simple-test :refer [str-test]])) 5 | 6 | (deftest dummy) ;; intellij seems to need this to recognize this file 7 | 8 | (str-test unification-type " 9 | a += X=&f(Z), X=&g(W), 1. % should unify to nothing, 10 | a += 0. 11 | 12 | %debug_repl a. 13 | 14 | assert a = 0. 15 | ") 16 | 17 | 18 | (str-test lessthan-combine " 19 | a := 1. 20 | a := 0 for A < B, B < A. 21 | 22 | assert a = 1. 23 | ") 24 | 25 | (str-test unification-partial-args " 26 | a += X=&f(1,Z), X=&f(Y,2), Z+Y. 27 | 28 | assert a = 3. 29 | ") 30 | 31 | (str-test combine-range " 32 | a += X for X >= 0, X < 10, int(X). 33 | 34 | assert a = 45.") 35 | 36 | 37 | (str-test agg-elem-branch-colon " 38 | fact(N) := fact(N-1) * N. 39 | fact(0) := 1. % if this branch matches, then we can stop processing the other branch which recurses 40 | 41 | assert fact(5) = 120. 42 | ") 43 | 44 | 45 | (str-test agg-elem-branch-max " 46 | rec_forever(X) = rec_forever(X). 47 | m(X) max= 0 for rec_forever(X). 48 | m(1) max= 1. % if this branch matches, then it can figure out that the other branch will not be used. additionally, having this property will give stuff like alpha-beta pruning \"for free\" 49 | 50 | assert m(1) = 1. 51 | ") 52 | 53 | (str-test alpha-beta-prune " 54 | foo(X) = foo(X). % we can not figure out the value of this because it keeps recursing 55 | 56 | maxs max= foo(X). 57 | maxs max= 1. % we can figure out that the value of this is at least 1 58 | 59 | mins min= 0. 60 | mins min= maxs. % this is going to be greater than the known 0 value 61 | 62 | assert mins = 0. 63 | ") 64 | 65 | 66 | (str-test saturated-or " 67 | foo(X) = foo(X). 68 | 69 | f |= foo(123). 70 | f |= true. 71 | 72 | assert f = true. 73 | ") 74 | 75 | (str-test saturated-any " 76 | foo(X) = foo(X). 77 | 78 | f ?= foo(123). 79 | f ?= 1. 80 | 81 | assert f = 1. 82 | ") 83 | 84 | (str-test type-structure-conflict " 85 | f := 1. 86 | f := 0 for X:int = foo[Z]. 87 | 88 | assert f = 1.") 89 | -------------------------------------------------------------------------------- /src/clojure/dyna/builtin_libraries/parser_generator.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.builtin-libraries.parser-generator 2 | (:require [instaparse.core :as insta]) 3 | (:require [dyna.base-protocols :refer :all]) 4 | (:require [dyna.rexpr :refer :all]) 5 | (:require [dyna.user-defined-terms :refer [def-user-term]]) 6 | (:import [dyna DynaTerm UnificationFailure])) 7 | 8 | ;; could use something like https://github.com/Engelberg/instaparse which would 9 | ;; allow for generating a parser on the fly from a string which defines the 10 | ;; rules. This would make it easy to define little DSLs in dyna by including 11 | ;; that as something. 12 | ;; being able to evaluate an expression 13 | 14 | 15 | 16 | (def-base-rexpr instaparse-construct-parser [:var grammar 17 | :var out]) 18 | 19 | (def-base-rexpr instaparse-run-parser [:var parser 20 | :var input 21 | :var out]) 22 | 23 | (def-rewrite 24 | :match (instaparse-construct-parser (:ground grammar) (:any out)) 25 | :assigns-variable out 26 | (let [g (get-value grammar)] 27 | (if-not (string? g) 28 | (throw (UnificationFailure. "grammar not represented as a string")) 29 | {:instaparse-parser (insta/parser g)}))) 30 | 31 | (defn convert-to-dyna [ast] 32 | (if (vector? ast) 33 | (DynaTerm. (.toLowerCase ^String (name (first ast))) (vec (map convert-to-dyna (rest ast)))) 34 | ast)) 35 | 36 | (def-rewrite 37 | :match (instaparse-run-parser (:ground parser) (:ground input) (:any out)) 38 | :assigns-variable out 39 | (let [parser (:instaparse-parser (get-value parser)) 40 | in (get-value input)] 41 | (if (or (nil? parser) (not (string? in))) 42 | (throw (UnificationFailure. "bad parser or not string input")) 43 | (convert-to-dyna (parser in))))) 44 | 45 | (def-user-term "$$__instaparse_construct_parser" 1 (make-instaparse-construct-parser v0 v1)) 46 | (def-user-term "$$__instaparse_run_parser" 2 (make-instaparse-run-parser v0 v1 v2)) 47 | 48 | ;(println "loaded parser generator clojure file") 49 | -------------------------------------------------------------------------------- /src/java/dyna/TracedNumber.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import java.util.Comparator; 4 | import java.util.PriorityQueue; 5 | import java.util.concurrent.Callable; 6 | 7 | public final class TracedNumber { 8 | 9 | public final double value; 10 | public final int height; 11 | 12 | public double accumulated_gradient; 13 | public final clojure.lang.IFn propagate_back; 14 | public boolean enqueued_in_backwards_computation = false; 15 | 16 | 17 | public double getValue() { 18 | return value; 19 | } 20 | 21 | public int getHeight() { 22 | return height; 23 | } 24 | 25 | public double getGradient() { 26 | return accumulated_gradient; 27 | } 28 | 29 | public void addToGradient(double v) { 30 | accumulated_gradient += v; 31 | } 32 | 33 | public TracedNumber(double val, int height, clojure.lang.IFn propagate) { 34 | value = val; 35 | this.height = height; 36 | propagate_back = propagate; // this will be called with the this parameter 37 | accumulated_gradient = 0; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return java.lang.Double.toString(value); 43 | } 44 | 45 | static final public Comparator height_compare = new Comparator() { 46 | @Override 47 | public int compare(TracedNumber t0, TracedNumber t1) { 48 | return t0.height - t1.height; 49 | } 50 | }; 51 | 52 | static public void runBackprop(TracedNumber loss) { 53 | loss.accumulated_gradient = 1; 54 | PriorityQueue queue = new PriorityQueue<>(loss.getHeight() + 1, height_compare); 55 | loss.enqueue(queue); 56 | while(!queue.isEmpty()) { 57 | TracedNumber t = queue.poll(); 58 | t.propagate_back.invoke(t, queue); 59 | } 60 | } 61 | 62 | public void enqueue(PriorityQueue q) { 63 | if(!enqueued_in_backwards_computation) { 64 | q.add(this); 65 | enqueued_in_backwards_computation = true; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /dyna_python_module/test/test_wrapper.py: -------------------------------------------------------------------------------- 1 | from dyna import Dyna, DynaTerm 2 | 3 | t = DynaTerm('test', 1,2,3) 4 | print(t) 5 | # print(t.name) 6 | # print(t[2]) 7 | assert t.name == 'test' 8 | assert t[2] == 3 9 | assert str(t) == 'test[1, 2, 3]' 10 | assert isinstance(t, DynaTerm) 11 | 12 | 13 | inst = Dyna() 14 | 15 | inst.run(''' 16 | print "hello from dyna". 17 | ''') 18 | 19 | inst.run(''' 20 | print str("hello ", $0). 21 | ''', 'world') 22 | 23 | inst.run(''' 24 | assert $0 == "world". 25 | ''', 'world') 26 | 27 | res = inst.query(''' 28 | foo = 123. 29 | 1 + 2? 30 | 4? 31 | ''') 32 | assert res[0] == 3 33 | assert res[1] == 4 34 | 35 | res2 = inst.query(''' 36 | 1 + $0? 37 | ''', 2) 38 | 39 | assert res2[0] == 3 40 | 41 | 42 | @inst.define_function() 43 | def my_function(a, b): 44 | print('my python function called') 45 | return a*3 + b 46 | 47 | 48 | res3 = inst.query(''' 49 | my_function($0, $1)? 50 | ''', 3, 4) 51 | 52 | assert res3[0] == my_function(3, 4) 53 | 54 | 55 | res4 = inst.query(''' 56 | &foo(1,2,3) ? 57 | ''') 58 | print(res4[0]) 59 | 60 | 61 | res5 = inst.query(''' 62 | [1,2,3,4] ? 63 | ''') 64 | print(res5[0]) 65 | assert type(res5[0]) is list 66 | 67 | res6 = inst.query(''' 68 | sum([]) = 0. 69 | sum([X|Y]) = X+sum(Y). 70 | sum($0)? 71 | ''', [1,2,3]) 72 | 73 | assert res6[0] == 6 74 | 75 | # test passing an opaque value through dyna 76 | class SomeTestClass: pass 77 | val = SomeTestClass() 78 | res7 = inst.query(''' 79 | $0 ? 80 | ''', val) 81 | assert res7[0] is val 82 | 83 | 84 | inst.run(''' 85 | testclass = $0. 86 | ''', val) 87 | 88 | res8 = inst.query(''' 89 | testclass? 90 | ''') 91 | assert res8[0] is val 92 | 93 | 94 | res9 = inst.query(''' 95 | % create a dynabase and return a handle which allows us to call methods on the dynabase from python 96 | { 97 | db_t1(X,Y) = 3 * X + Y + $0. 98 | } ? 99 | ''', 7) 100 | 101 | assert res9[0].db_t1(3,4) == 3*3 + 4 + 7 102 | 103 | 104 | res10 = inst.query(''' 105 | { "A" -> 1, 106 | "B" -> 2 } ? 107 | ''') 108 | 109 | assert res10[0] == {"A": 1, "B": 2} 110 | -------------------------------------------------------------------------------- /src/java/dyna/SimplifyRewriteCollection.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.AFn; 4 | import clojure.lang.IFn; 5 | import clojure.lang.Var; 6 | import clojure.lang.RT; 7 | 8 | public final class SimplifyRewriteCollection extends AFn { 9 | 10 | public static class RewriteList { 11 | final IFn rewrite_func; 12 | final RewriteList next; 13 | private RewriteList(IFn rewrite_func, RewriteList next) { 14 | this.rewrite_func = rewrite_func; 15 | this.next = next; 16 | } 17 | } 18 | 19 | private RewriteList head = null; 20 | private final boolean make_new_rewrites; 21 | 22 | public RewriteList getRewriteListHead() { return head; } 23 | 24 | private SimplifyRewriteCollection(boolean make_new_rewrites) { 25 | this.make_new_rewrites = make_new_rewrites; 26 | } 27 | 28 | static public SimplifyRewriteCollection create(boolean make_new_rewrites) { 29 | return new SimplifyRewriteCollection(make_new_rewrites); 30 | } 31 | 32 | public Rexpr doRewrite(Rexpr r, IFn simplify_method) { 33 | RewriteList h = head; 34 | if(h == null) 35 | return makeNewRewrites(r, simplify_method); 36 | while(h != null) { 37 | Rexpr result = (Rexpr)h.rewrite_func.invoke(r, simplify_method); 38 | if(result != null && r != result && !r.equals(result)) { 39 | return result; 40 | } 41 | h = h.next; 42 | } 43 | return r; 44 | } 45 | 46 | public Rexpr makeNewRewrites(Rexpr r, IFn simplify_method) { 47 | if(make_new_rewrites) 48 | return (Rexpr)jit_create_rewrite.invoke(r); 49 | else 50 | return r; 51 | } 52 | 53 | public synchronized void addRewrite(IFn rewrite_func) { 54 | head = new RewriteList(rewrite_func, head); 55 | } 56 | 57 | @Override 58 | public Rexpr invoke(Object r, Object simplify_method) { 59 | return doRewrite((Rexpr)r, (IFn)simplify_method); 60 | } 61 | 62 | private static Var jit_create_rewrite = RT.var("dyna.rexpr-jit-v2", "simplify-jit-attempt-create-rewrite-for-jittype"); 63 | //RT.var("dyna.rexpr", "simplify-jit-create-rewrite"); 64 | } 65 | -------------------------------------------------------------------------------- /dyna_python_module/test/Notebook test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "id": "2660feb8", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from dyna import Dyna\n", 11 | "\n", 12 | "d = Dyna()" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 7, 18 | "id": "7d6c2fe5", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "%%dyna\n", 23 | "\n", 24 | "fib(N) := fib(N-1) + fib(N-2).\n", 25 | "fib(0) := 0.\n", 26 | "fib(1) := 1." 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "ab6fdad7", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "%%dyna\n", 37 | "\n", 38 | "% fib(5)?" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 6, 44 | "id": "515c277c", 45 | "metadata": {}, 46 | "outputs": [ 47 | { 48 | "data": { 49 | "text/html": [ 50 | "
3
9
" 51 | ], 52 | "text/plain": [ 53 | "[3, 9]" 54 | ] 55 | }, 56 | "execution_count": 6, 57 | "metadata": {}, 58 | "output_type": "execute_result" 59 | } 60 | ], 61 | "source": [ 62 | "%%dyna\n", 63 | "\n", 64 | "1 + 2?\n", 65 | "\n", 66 | "4 + 5?" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "3acdcf21", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 3 (ipykernel)", 81 | "language": "python", 82 | "name": "python3" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 3 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython3", 94 | "version": "3.10.9" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 5 99 | } 100 | -------------------------------------------------------------------------------- /dyna_python_module/test/.ipynb_checkpoints/Notebook test-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "id": "2660feb8", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from dyna import Dyna\n", 11 | "\n", 12 | "d = Dyna()" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 7, 18 | "id": "7d6c2fe5", 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "%%dyna\n", 23 | "\n", 24 | "fib(N) := fib(N-1) + fib(N-2).\n", 25 | "fib(0) := 0.\n", 26 | "fib(1) := 1." 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "ab6fdad7", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "%%dyna\n", 37 | "\n", 38 | "% fib(5)?" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 6, 44 | "id": "515c277c", 45 | "metadata": {}, 46 | "outputs": [ 47 | { 48 | "data": { 49 | "text/html": [ 50 | "
3
9
" 51 | ], 52 | "text/plain": [ 53 | "[3, 9]" 54 | ] 55 | }, 56 | "execution_count": 6, 57 | "metadata": {}, 58 | "output_type": "execute_result" 59 | } 60 | ], 61 | "source": [ 62 | "%%dyna\n", 63 | "\n", 64 | "1 + 2?\n", 65 | "\n", 66 | "4 + 5?" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "id": "3acdcf21", 73 | "metadata": {}, 74 | "outputs": [], 75 | "source": [] 76 | } 77 | ], 78 | "metadata": { 79 | "kernelspec": { 80 | "display_name": "Python 3 (ipykernel)", 81 | "language": "python", 82 | "name": "python3" 83 | }, 84 | "language_info": { 85 | "codemirror_mode": { 86 | "name": "ipython", 87 | "version": 3 88 | }, 89 | "file_extension": ".py", 90 | "mimetype": "text/x-python", 91 | "name": "python", 92 | "nbconvert_exporter": "python", 93 | "pygments_lexer": "ipython3", 94 | "version": "3.10.9" 95 | } 96 | }, 97 | "nbformat": 4, 98 | "nbformat_minor": 5 99 | } 100 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/static_key_map.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.static-key-map 2 | (:require [dyna.utils :refer :all])) 3 | 4 | ;; A map which will hold all of the 5 | 6 | (defn build-map-class [name arguments] 7 | (let [clsname (gensym 'mapcls) 8 | argmap (into {} (for [k arguments] 9 | [k (gensym (str k))])) 10 | argvars (vec (vals argmap)) 11 | c `(do 12 | (ns dyna.static-key-map) 13 | (deftype ~clsname ~(vec (map #(with-meta % {:unsynchronized-mutable true}) argvars)) 14 | clojure.lang.ITransientAssociative 15 | (assoc ~'[this key val] 16 | (case ~'key 17 | ~@(apply concat (for [k v] 18 | [k `(do (set! ~v ~'val) 19 | ~'val)])) 20 | (throw (java.util.NoSuchElementException.)))) 21 | (without ~'[this key] (???)) 22 | (persistent ~'[this] ~'this) 23 | (count ~'[this] ~(count arguments)) 24 | (conj ~'[this [key val]] ~'(assoc this key val)) 25 | (valAt ~'[this key] 26 | (case ~'key 27 | ~@(apply concat (for [[k v] argmap] 28 | [k v])) 29 | (throw (java.util.NoSuchElementException.)))) 30 | (valAt ~'[this key notfound] 31 | (case ~'key 32 | ~@(apply concat (for [[k v] argmap] 33 | [k v])) 34 | notfound)) 35 | Object 36 | (hashCode [this] 37 | (???)) 38 | (equals [this other] 39 | (???)) 40 | (toString [this] 41 | "TODO: string for static map")) 42 | {:class ~clsname 43 | :make-empty (fn [] (~(symbol (str clsname ".")) ~@(for [x argvars] nil))) 44 | :make (fn [args] 45 | (~(symbol (str clsname ".")) 46 | ~@(for [[k v] argmap] 47 | ~(get ~'args ~k))))} 48 | )] 49 | (eval c))) 50 | 51 | (defn build-map [& args] 52 | (let [m (apply hash-map args)] 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /src/java/dyna/StatusCounters.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | public class StatusCounters { 4 | static private long rewrites_performed = 0; 5 | static private long matches_attempted = 0; 6 | static private long matches_successful = 0; 7 | static private long rexprs_created = 0; 8 | //static private long primitive_builtin_evaluated = 0; 9 | static private long jit_rewrites_performed = 0; 10 | 11 | static private long program_start_time = 0; 12 | static private long agenda_work_processed = 0; 13 | static private long agenda_time_processing = 0; 14 | 15 | private StatusCounters() {} 16 | 17 | public static long get_matches_attempted() { return matches_attempted; } 18 | public static long get_rewrites_performed() { return rewrites_performed + jit_rewrites_performed; } 19 | public static long get_rexpr_created() { return rexprs_created; } 20 | 21 | public static void print_counters() { 22 | String p = "----------------------------------------------------\n" + 23 | "Rewrites performed: " + (rewrites_performed+jit_rewrites_performed) + "\n"; 24 | if(jit_rewrites_performed != 0) { 25 | p += "JIT generated rewrites performed: " + jit_rewrites_performed + " ("+String.format("%.2f", ((double)jit_rewrites_performed*100)/(rewrites_performed+jit_rewrites_performed))+"%)\n"; 26 | } 27 | p += "Matches attempted: " + matches_attempted + "\n" + 28 | "Matches successful: " + (matches_successful+jit_rewrites_performed) + "\n" + 29 | "Rexprs created: " + rexprs_created + "\n"; 30 | if(program_start_time != 0) { 31 | String runtime = String.format("%.2f", (System.currentTimeMillis() - program_start_time) / 1000.0); 32 | p += "Program run time: " + runtime + " (secs) \n"; 33 | } 34 | System.out.print(p); 35 | } 36 | 37 | public static void rewrite_performed() { rewrites_performed++; } 38 | public static void match_attempt() { matches_attempted++; } 39 | public static void match_sucessful() { matches_successful++; } 40 | public static void rexpr_created() { rexprs_created++; } 41 | public static void program_start() { program_start_time = System.currentTimeMillis(); } 42 | public static void jit_rewrite_performed() { jit_rewrites_performed++; } 43 | 44 | public static void agenda_processing_time(long work, long time) { 45 | agenda_work_processed += work; 46 | agenda_time_processing += time; 47 | } 48 | 49 | public static final boolean run_counters = true;//Boolean.parseBoolean(System.getProperty("dyna.status_counters", "true")); 50 | } 51 | -------------------------------------------------------------------------------- /test/dyna/programs_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.programs-test 2 | (:require [dyna.core]) 3 | (:require [dyna.simple-test :refer [str-test]]) 4 | (:require [clojure.test :refer :all]) 5 | (:require [clojure.java.io :refer [as-file as-relative-path]]) 6 | (:require [dyna.system :refer [make-new-dyna-system run-under-system]]) 7 | (:require [dyna.ast-to-rexpr :refer [import-file-url]]) 8 | ;(:require [dyna.utils :refer [debug-repl]]) 9 | (:import [dyna DynaUserAssert])) 10 | 11 | 12 | (def test-dir (as-file "./test_programs")) 13 | ;;(debug-repl) 14 | 15 | (defn run-file [fname] 16 | (let [sstate (make-new-dyna-system)] 17 | (try 18 | (run-under-system sstate 19 | (import-file-url (.toURL (as-file (str "./test_programs/" fname ".dyna"))))) 20 | (is true) 21 | (catch DynaUserAssert e 22 | (is false) 23 | (throw e))))) 24 | 25 | (deftest dummy) ;; for intellij 26 | 27 | (defmacro make-file-test [fname] 28 | `(deftest ~(symbol fname) 29 | (run-file ~(str fname)))) 30 | 31 | (make-file-test "basic1") 32 | (make-file-test "edit_distance") 33 | (make-file-test "quicksort") 34 | (make-file-test "simple_matrix") 35 | (make-file-test "import_file") 36 | (make-file-test "parser_generator_test") 37 | (make-file-test "cky_parsing2") 38 | 39 | 40 | ;; for some reason, attempting to do file io in the macro causes the loading of other unrelated stuff to not work 41 | ;; think this is probably some issue inside of the clojure compiler 42 | (comment 43 | (print `(do 44 | (print "did eval of files") 45 | ~@(for [f (file-seq test-dir)] 46 | (let [name (.getName f)] 47 | (comment (when (and (.isFile f) (.endsWith name ".dyna")) 48 | (let [fname (.substring name 0 (- (.length name) 5))] 49 | ;`(make-file-test ~(str fname)) 50 | ))))))) 51 | 52 | (defmacro get-file-tests [] 53 | (let [fd (java.io.File. "./test_programs") 54 | names (.list fd)] 55 | `(do ~@(for [name names] 56 | (when (.endsWith name ".dyna") 57 | (let [fname (.substring name 0 (- (.length name) 5))] 58 | `(make-file-test ~fname)))))))) 59 | 60 | ;(get-file-tests) 61 | 62 | 63 | ;; (let [fd (java.io.File. "./test_programs") 64 | ;; fn (.list fd)] 65 | ;; (eval `(do ~@(for [f fn] 66 | ;; (when (.endsWith f ".dyna") 67 | ;; `(make-file-test f)))))) 68 | 69 | ;; (doseq [f (file-seq test-dir)] 70 | ;; (Thread/sleep 5)) 71 | 72 | ;; (doseq [f (file-seq test-dir)] 73 | ;; (print f)) 74 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject dyna "0.1.0-SNAPSHOT" 2 | :description "Dyna built on R-exprs" 3 | :url "https://github.com/argolab/dyna3" 4 | :license {:name "LGPL-3.0-or-later WITH Classpath-exception"} 5 | :dependencies [[org.clojure/clojure "1.11.1" ;"1.12.0-mfl-SNAPSHOT" 6 | ] 7 | [org.clojure/tools.namespace "1.2.0"] 8 | ;[org.ow2.asm/asm "9.5"] 9 | ;[org.clojure/tools.macro "0.1.5"] 10 | [aprint "0.1.3"] ;; for formatted printing 11 | ;[clj-python/libpython-clj "2.00-beta-22"] 12 | ;[com.clojure-goes-fast/clj-java-decompiler "0.3.0"] ;; for debugging what the generated code looks like 13 | [org.antlr/antlr4-runtime "4.7.2"] ;; for the front end parser 14 | [org.jline/jline "3.20.0"] ;; for the front end repl 15 | [robert/hooke "1.3.0"] 16 | ;[jise "0.1.0-SNAPSHOT"] ;; can get more control over the generated java classes 17 | ;[datalevin "0.6.6"] 18 | ;[io.replikativ/datahike "0.5.1504"] 19 | 20 | ;[org.clojure/tools.analyzer.jvm "0.7.3"] 21 | [instaparse "1.4.12"] 22 | 23 | [org.clojure/data.csv "1.0.1"] 24 | ;[reply "0.5.1"] 25 | ] 26 | :repl-options {:init-ns dyna.core} 27 | :source-paths ["src/clojure"] 28 | :java-source-paths ["target/gen-src" "src/java"] 29 | :resource-paths ["src/resources"] 30 | :test-paths ["test"] 31 | ;:profiles {:uberjar {:aot :all}} 32 | ;;:profiles {:uberjar {:aot [dyna.rexpr]}} 33 | :plugins [[lein-antlr-plugin "0.1.0"] 34 | [me.shalakapatil/retest-failures "0.1.0-SNAPSHOT" :hooks false]] 35 | :antlr-src-dir "src/antlr" 36 | :antlr-dest-dir "target/gen-src" 37 | 38 | :profiles {:uberjar {:main dyna.DynaMain}} 39 | :main dyna.core 40 | 41 | ;; clojure only requires java 8, and we shouldn't need any of the "newer" features ourselves also 42 | ;; so this should make this work with any version released after 1.8 (march 2014) 43 | :javac-options ["-source" "1.8" "-target" "1.8" "-XDignore.symbol.file" "-Xlint:-options"] 44 | 45 | ;; this is needed to generate the uberjar, but it makes it slower to run when working on stuff 46 | ;; so can comment out if running tests or something from the local directory 47 | :aliases {"compile" ["do" ["antlr"] ["javac"] "compile"] 48 | "uberjar" ["do" ["compile"] "uberjar"]} 49 | 50 | 51 | ;;:main dyna.DynaMain 52 | :global-vars {;*warn-on-reflection* true ;; useful for identifying where it uses clojure's reflection which is slow... 53 | ;*unchecked-math* :warn-on-boxed ;; boxed math is slow 54 | } 55 | 56 | ;; this will check the repo every time it runs... 57 | ;:repositories [["matthewfl.com" "https://matthewfl.com/maven-repo"]] 58 | ) 59 | -------------------------------------------------------------------------------- /dyna_python_module/dyna/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Dyna programming language Python API. 3 | """ 4 | 5 | import io as _io 6 | import inspect 7 | 8 | # this will configure the JVM, and start loading the dyna runtime in the background concurrently 9 | from . import java_configure_jvm 10 | 11 | 12 | class Dyna: 13 | 14 | def __init__(self): 15 | self.__system_v = None 16 | 17 | @property 18 | def __system(self): 19 | v = self.__system_v 20 | if v is not None: 21 | return v 22 | # this will reference the python class which wrap the java class. This will block until the dyna runtime is loaded 23 | from .java_wrapper import DynaInstance 24 | v = DynaInstance() 25 | self.__system_v = v 26 | return v 27 | 28 | def define_function(self, name=None, arity=None): 29 | """ 30 | A decorator which allows for python functions to be callable from dyna 31 | """ 32 | used = False 33 | def f(func): 34 | nonlocal name, arity, used 35 | assert used is False, "The function names & arity must be unique" 36 | used = True 37 | if name is None: 38 | name = func.__name__ 39 | if arity is None: 40 | arity = len(inspect.getargspec(func).args) 41 | self.__system.define_function(name, arity, func) 42 | return func 43 | return f 44 | 45 | def execute(self, code: str, *value_args): 46 | """ 47 | Run a string of code with additional arguments corresponding to values represented as $0,$1,...$n in the code 48 | Returns Nothing 49 | 50 | Any queries made under execute will be printed out only, nothing will be returned 51 | """ 52 | self.__system.run_string(code, *value_args) 53 | 54 | def query(self, code, *value_args): 55 | """ 56 | Return a string of code with additional arguments corresponding to values represented as $0,$1,....$n in the code 57 | Returns an array of queries made in the program. 58 | 59 | Queries will be returned and not printed out 60 | """ 61 | return self.__system.run_query(code, *value_args) 62 | 63 | def run(self, code, *value_args): 64 | """ 65 | Run a string or a file of code 66 | """ 67 | if isinstance(code, (_io.TextIOBase, _io.BufferedIOBase, _io.RawIOBase, _io.IOBase)) and not value_args: 68 | return self.run_file(code) 69 | else: 70 | return self.execute(code, *value_args) 71 | 72 | def run_file(self, file): 73 | self.__system.run_file(file) 74 | 75 | 76 | class _DynaTermMetaClass(type): 77 | 78 | def __instancecheck__(cls, obj): 79 | from .java_wrapper import DynaTerm 80 | return isinstance(obj, DynaTerm) 81 | 82 | class DynaTerm(metaclass=_DynaTermMetaClass): 83 | 84 | def __new__(cls, *args, **kwargs): 85 | from .java_wrapper import DynaTerm 86 | return DynaTerm(*args, **kwargs) 87 | 88 | 89 | __all__ = [ 90 | 'Dyna', 91 | 'DynaTerm', 92 | ] 93 | 94 | 95 | try: 96 | from . import dyna_ipython_magic 97 | except (ImportError,NameError): 98 | # if ipython is not being used, then the import of this will fail 99 | pass 100 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LEIN := $(shell which lein 2>/dev/null > /dev/null && echo lein || { if [ ! -f .lein.sh ]; then curl -o .lein.sh https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein; chmod +x .lein.sh ; fi; echo './.lein.sh' ; }) 2 | JAVA ?= java 3 | 4 | # this version should match the version in project.clj as that is what is going to be built by lein 5 | JVERSION:= 0.1.0 6 | VERSION:= $(shell git describe --always --long --abbrev=12)-$(shell date '+%Y%m%d') 7 | 8 | SOURCE=$(wildcard src/*/*/*.clj) $(wildcard src/*/*/*.java) 9 | JAR_TARGET=target/dyna-$(JVERSION)-SNAPSHOT-standalone.jar 10 | JAR_WITH_PYTHON_INSTALLER=target/dyna-combined-$(JVERSION)-SNAPSHOT-standalone.jar 11 | TARGET=dyna-standalone-$(VERSION).run 12 | 13 | PYTHON_MODULE=python_module 14 | 15 | PARSER_TARGET=target/classes/dyna/dyna_grammar2Parser.class 16 | 17 | .PHONY: clean all repl test 18 | 19 | all: $(TARGET) 20 | 21 | clean: 22 | rm -rf target/ dyna-standalone-* python_build/ 23 | 24 | test: 25 | _JAVA_OPTIONS='-Ddyna.debug=false -Ddyna.trace_rexpr_construction=false -Ddyna.debug_repl=false -Xss8m -ea' $(LEIN) test 26 | 27 | test-debug: 28 | $(LEIN) test 29 | 30 | # some of the tests are failing randomly or somehow failing depending on the order in which they run. Trying to fix this, but annoying right now with github running of the tests failing 31 | github-test: 32 | _JAVA_OPTIONS='-Ddyna.debug=false -Ddyna.trace_rexpr_construction=false -Ddyna.debug_repl=false -Xss8m -ea' $(LEIN) test || _JAVA_OPTIONS='-Ddyna.debug=false -Ddyna.trace_rexpr_construction=false -Ddyna.debug_repl=false -Xss8m -ea' $(LEIN) retest 33 | 34 | # start the repl for dyna code from the source directory 35 | repl: dyna-repl 36 | dyna-repl: $(PARSER_TARGET) 37 | $(JAVA) -cp `$(LEIN) classpath` dyna.DynaMain 38 | 39 | 40 | # start the repl for clojure code 41 | clj-repl: clean 42 | $(LEIN) repl 43 | 44 | 45 | # if we are building the uberjar, then run the clean first as some of the macroexpands might have changed 46 | # and we don't want to have mixed the old and new versions of this 47 | $(JAR_TARGET): $(SOURCE) 48 | rm -rf target/ 49 | $(LEIN) uberjar 50 | 51 | $(PARSER_TARGET): src/antlr/dyna/dyna_grammar2.g4 52 | $(LEIN) do antlr, javac, compile 53 | 54 | $(JAR_WITH_PYTHON_INSTALLER): $(JAR_TARGET) $(wildcard dyna_python_module/**/*.py) 55 | cp $(JAR_TARGET) $(JAR_WITH_PYTHON_INSTALLER) 56 | find dyna_python_module/ -name '*.pyc' -delete 57 | jar -uf $(JAR_WITH_PYTHON_INSTALLER) dyna_python_module 58 | 59 | $(TARGET): $(JAR_WITH_PYTHON_INSTALLER) standalone-header.sh 60 | cat standalone-header.sh | sed "s/VERSION_STRING/$(shell git describe --always --long --dirty --abbrev=12 ; date)/" > $(TARGET) 61 | cat $(JAR_WITH_PYTHON_INSTALLER) >> $(TARGET) 62 | chmod +x $(TARGET) 63 | 64 | test-python: $(JAR_TARGET) 65 | python dyna_python_module/test/test_wrapper.py 66 | 67 | run-class-path: 68 | @$(LEIN) classpath | awk '/\.jar/' 69 | 70 | python-package: $(TARGET) 71 | @echo 'run "pip install build" first' 72 | rm -rf python_build/ 73 | cp -r dyna_python_module python_build/ 74 | cp $(TARGET) python_build/dyna/dyna.jar 75 | cd python_build && python -m build 76 | 77 | # example to run a single test 78 | # reset && rlwrap -a lein test :only dyna.core-test/basic-aggregator2 79 | # 80 | # reset b/c lein messes with teh terminal settings, 81 | # rlwrap -a because lein does not echo what is input into the temrinal when running the tests 82 | -------------------------------------------------------------------------------- /src/resources/dyna/builtin_libraries/state_transducer.dyna: -------------------------------------------------------------------------------- 1 | :- export state_transducer/1. 2 | :- export epsilon/0. 3 | 4 | % this is like a FST, but we are not having "finite states" as the states are allowed to be represented using dyna rules, which means that q 5 | 6 | epsilon = &epsilon. 7 | 8 | state_transducer(X: dynabase) := state_transducer_dynabase(X). 9 | state_transducer(X: string) := state_transducer_str(X). 10 | 11 | 12 | % there could be a special semiring argument? Though we might also want to 13 | state_transducer_dynbase(Wrapped) = { 14 | edge(From, To, InLabel, OutLabel) = Wrapped.edge(From, To, InLabel, OutLabel). 15 | start = Wrapped.start. 16 | end(X) = Wrapped.end(X). 17 | 18 | 19 | compose(Other) = state_transducer_dynabase { 20 | % This would still have to handle epsilon labels on the edges, as those could also align together 21 | edge(&c(FA, FB), &c(TA, TB), InLabel, OutLabel) += Self.edge(FA, TA, InLabel, InterLabel) * Other.edge(FB, TB, InterLabel, OutLabel). 22 | start = &c(Self.start, Other.start). 23 | end(&c(A, B)) = Self.end(A) * Other.end(B). 24 | } for Self=$self. 25 | 26 | num_edges += 1 for _ = $self.edge(_,_,_,_). 27 | states(X) :- _ = $self.edge(X,_,_,_). 28 | states(X) :- _ = $self.edge(_,X,_,_). 29 | states($self.start). 30 | states(X) :- _ = $self.end(X). 31 | num_states += 1 for states(X). 32 | 33 | remove_epsilon = state_transducer_dynabase { 34 | start = Self.start. 35 | end(X) = Self.end(X). 36 | 37 | node_remove_epsilon(A, A) *= 1. 38 | node_remove_epsilon(From, To) *= Self.edge(From, T1, epislon, epislon) * $self.node_remove_epsilon(T1, To). 39 | edge(From, To, IL, OL) = Self.edge(From, T1, IL, OL) * $self.node_remove_epsilon(T1, To) for (IL != epislon || OL != epislon). 40 | } for Self=$self. 41 | 42 | % minimize = state_transducer_dynabase { 43 | 44 | % } for Self=$self. 45 | 46 | concat(Other) = state_transducer_dynabase { 47 | start = a[Self.start]. 48 | end(b[X]) = Other.end(X). 49 | edge(a[From], a[To], InLabel, OutLabel) = Self.edge(From, To, InLabel, OutLabel). 50 | edge(b[From], b[To], InLabel, OutLabel) = Other.edge(From, To, InLabel, OutLabel). 51 | edge(a[From], b[Other.start], epsilon, epsilon) = Self.end(From). 52 | } for Self=$self. 53 | 54 | % determinize = state_transducer_dynabase { 55 | % start = Self.start. 56 | 57 | % } for Self=$self. 58 | 59 | intersect(Other) = state_transducer_dynabase { 60 | start = c[Self.start, Other.start]. 61 | end(c[A,B]) = Self.end(A) * Other.end(B). 62 | % this would have to handle epsilon somehow? 63 | edge(c[From1, From2], c[To1, To2], InLabel, OutLabel) = Self.edge(From1, To1, InLabel, OutLabel) * Other.edge(From2, To1, InLabel, OutLabel). 64 | } for Self=$self. 65 | 66 | }. 67 | 68 | 69 | % want to write something like: 70 | % f = fst'{ 71 | % 0 1 The The 72 | % 1 2 cat cat 73 | % 2 3 sat sat 74 | % 3 4 on on 75 | % 4 5 the the 76 | % 5 6 mat mat 77 | % 6 ! 78 | % }. 79 | % 80 | % possibly matching the openfst file format? https://www.openfst.org/twiki/bin/view/FST/FstExamples 81 | 82 | state_transducer_str(Str) = state_transducer_dynabase { 83 | start = start[]. 84 | end(X) = 1 for $regex_match(Str, "(\w+) !", _, X). 85 | edge(From, To, InLabel, OutLabel) = 1 for $regex_match(Str, "(\w+) (\w+) (\w+) (\w+)", _, From, To, InLabel, OutLabel). 86 | }. 87 | 88 | fst_str(X) = &error. -------------------------------------------------------------------------------- /test/dyna/memoization_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.memoization-test 2 | (:require [dyna.core]) 3 | (:require [dyna.simple-test :refer [str-test]])) 4 | 5 | 6 | #_(str-test memoization1 " 7 | foo(1) += 123. 8 | foo(2) += 456. 9 | 10 | :- memoize_unk foo/1. 11 | 12 | assert foo(1) = 123. 13 | ") 14 | 15 | #_(str-test memoization2 " 16 | :- memoize_unk f/1. 17 | f(1) = 1. 18 | 19 | assert f(1) = 1. 20 | 21 | :- print_memo_table f/1. 22 | ") 23 | 24 | #_(str-test memoization3 " 25 | :- memoize_unk f/1. 26 | f(1) = 1. 27 | 28 | assert f(1) = 1. 29 | f(2) = 2. 30 | 31 | %:- run_agenda. 32 | %:- print_memo_table f/1. 33 | 34 | assert f(2) = 2. 35 | ") 36 | 37 | #_(str-test factorial " 38 | fact(0) := 1. 39 | fact(N) := fact(N-1)*N for N > 0. 40 | 41 | :- memoize_unk fact/1. 42 | 43 | assert fact(5) = 120.") 44 | 45 | 46 | (str-test fibonacci " 47 | fib(0) := 0. 48 | fib(1) := 1. 49 | fib(N) := fib(N-2) + fib(N-1) for N > 1. 50 | 51 | $memo(fib[N:$ground]) = \"unk\". 52 | 53 | print fib(3). 54 | 55 | print fib(10). 56 | 57 | assert fib(10) = 55. 58 | 59 | %print fib(100). 60 | %debug_repl X=fib(100), Z = 354224848179261915075. 61 | 62 | % if this is not memoized, then this will take too long to run 63 | assert fib(100) = 354224848179261915075. 64 | 65 | ") 66 | 67 | #_(str-test factorial-null " 68 | :- memoize_null fact/1. 69 | fact(0) := 1. 70 | fact(N) := fact(N-1)*N for N < 4. 71 | 72 | :- run_agenda. 73 | 74 | %debug_repl fact(2). 75 | 76 | assert fact(2) = 2. 77 | 78 | :- print_memo_table fact/1. 79 | ") 80 | 81 | 82 | (str-test memoization-v2 " 83 | fact(N) := fact(N-1)*N for N > 0. 84 | fact(0) := 1. 85 | 86 | 87 | $memo(fact[N:$ground]) = \"unk\". 88 | 89 | assert fact(10) = 3628800. 90 | ") 91 | 92 | ;; check that we can have the aggregator not go back and compute negative numbers 93 | (str-test memoization-v2-t2 " 94 | fact(N) := fact(N-1)*N. 95 | fact(0) := 1. 96 | 97 | 98 | $memo(fact[N:$ground]) = \"unk\". 99 | 100 | print fact(100). 101 | 102 | %assert fact(10) = 3628800. 103 | assert fact(4) = 24. 104 | ") 105 | 106 | (str-test memoization-v2-iter " 107 | f(X) := X*3 for range(0,10,X). 108 | 109 | $memo(f[X:$free]) = \"null\". 110 | 111 | g += f(X). 112 | print g. 113 | assert g = 135. 114 | ") 115 | 116 | (str-test memoization-v2-big-fact " 117 | fact(N) := fact(N-1)*N. 118 | fact(0) := 1. 119 | 120 | $memo(fact[N:$ground]) = \"unk\". 121 | 122 | print fact(2000). % there was a bug in the hash table which caused an extra factor of O(N) get in there. that is hard to test in the 123 | ") 124 | 125 | 126 | (str-test priority1 " 127 | fact(N) := fact(N-1)*N. 128 | fact(0) := 1. 129 | 130 | $memo(fact[N:$ground]) = \"unk\". 131 | 132 | $priority(fact[N]) = -N. 133 | 134 | print fact(20). 135 | ") 136 | 137 | (str-test priority2 " 138 | % this is a bad order which causes the most amount of repropagation 139 | fib_bad(0) += 0. 140 | fib_bad(1) += 1. 141 | fib_bad(N) += fib_bad(N-1) for N > 1. 142 | fib_bad(N) += fib_bad(N-2) for N > 1. 143 | 144 | $memo(fib_bad[N:$ground]) = \"unk\". 145 | $priority(fib_bad[N]) = N. 146 | 147 | print fib_bad(10). 148 | assert fib_bad(10) == 55. 149 | ") 150 | 151 | (str-test memo-change-program " 152 | fib(0) += 0. 153 | fib(1) += 1. 154 | fib(N) += fib(N-1) for N > 1. 155 | fib(N) += fib(N-2) for N > 1. 156 | 157 | $memo(fib[N:$ground]) = \"unk\". 158 | 159 | assert fib(10) = 55. 160 | 161 | fib(4) += 1. % this causes the program's R-expr to change, so the memo table has to get rebuilt 162 | 163 | assert fib(10) = 68. 164 | ") 165 | -------------------------------------------------------------------------------- /src/java/dyna/DynaMain.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.java.api.Clojure; 4 | import clojure.lang.IFn; 5 | import clojure.lang.Var; 6 | 7 | class DynaMain { 8 | public static final long starting_time = System.currentTimeMillis(); 9 | private static boolean is_loading = false; 10 | private static final Object loading_lock = new Object(); 11 | 12 | public static void main(String[] args) { 13 | // the time to first print is quite long, if we wanted, we could start the compilation in a thread 14 | // and just print the "normal" stuff for the first few seconds while waiting for the compile to complete in the thread 15 | // it would have to have some blocking methods before it would be allowed to call into the runtime 16 | 17 | is_loading = true; 18 | if(Boolean.getBoolean("dyna.loading_spin")) { 19 | System.out.print("\033[?25l"); // hide the cursor 20 | Thread t = new Thread(new Runnable () { 21 | public void run() { 22 | int step = 0; 23 | while(is_loading) { 24 | String s = "Loading... "; 25 | switch(step++ %4) { 26 | case 0: s += "/"; break; 27 | case 1: s += "-"; break; 28 | case 2: s += "\\"; break; 29 | case 3: s += "|"; break; 30 | } 31 | System.out.print(s + "\r"); 32 | System.out.flush(); 33 | try { 34 | Thread.sleep(90); 35 | } catch(InterruptedException e) { break; } 36 | } 37 | } 38 | }); 39 | t.setDaemon(true); 40 | t.start(); 41 | } 42 | 43 | try { 44 | initRuntime(); // do the actual init of the runtime, this function takes a few seconds 45 | } finally { 46 | is_loading = false; 47 | if(Boolean.getBoolean("dyna.loading_spin")) { 48 | // show the cursor again and clear the line 49 | System.out.print("\033[?25h\r\033[K"); 50 | } 51 | } 52 | 53 | IFn main_function = Clojure.var("dyna.core", "main"); // invoke the main method with the arguments now 54 | main_function.invoke(args); 55 | } 56 | 57 | public static void initRuntime() { 58 | // anything about setting up the clojure runtime before we begin or loading the files should be done here 59 | // then we can call this from other places which might serve as entry points to the runtime 60 | synchronized (loading_lock) { 61 | is_loading = true; 62 | if(Boolean.getBoolean("dyna.unchecked_math")) { 63 | ((Var)Clojure.var("clojure.core", "*unchecked-math*")).bindRoot(true); 64 | } 65 | 66 | Clojure.var("clojure.core", "load").invoke("/dyna/core"); // load all of the files 67 | is_loading = false; 68 | } 69 | } 70 | 71 | public static void backgroundInit() { 72 | // the runtime takes a few seconds to load. So from the Python wrapper, 73 | // we can start that loading in the background without blocking the main 74 | // python thread 75 | Thread t = new Thread(new Runnable () { 76 | public void run() { 77 | initRuntime(); 78 | } 79 | }); 80 | t.start(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/clojure/dyna/public_interface.clj: -------------------------------------------------------------------------------- 1 | ;; this file is a public interface to the dyna runtime. Assuming that someone 2 | ;; is not using the repl (defined in repl.clj), then we should be able to assume 3 | ;; that all interaction with the system will run through this interface 4 | 5 | (ns dyna.public-interface 6 | (:require [dyna.core]) 7 | (:require [dyna.utils :refer :all]) 8 | (:require [dyna.system :as system]) 9 | (:require [dyna.base-protocols :refer [is-bound-in-context? get-value-in-context is-empty-rexpr?]]) 10 | (:require [dyna.rexpr :refer [construct-rexpr make-variable make-function-call]]) 11 | (:require [dyna.ast-to-rexpr :refer [import-file-url 12 | eval-string 13 | eval-ast 14 | current-dir]]) 15 | (:require [dyna.user-defined-terms :refer [add-to-user-term!]]) 16 | (:import [dyna DynaInterface ExternalFunction DynaTerm])) 17 | 18 | 19 | (defmacro maybe-sys [sys & body] 20 | `(if (nil? ~sys) 21 | (do ~@body) 22 | (system/run-under-system ~sys ~@body))) 23 | 24 | (defn run-string [sys prog external-values] 25 | (maybe-sys sys 26 | (binding [system/parser-external-value (fn [index] 27 | (get external-values index))] 28 | (eval-string prog)))) 29 | 30 | (defn run-file [sys prog] 31 | (maybe-sys sys 32 | (import-file-url prog))) 33 | 34 | (defn run-query [sys query external-values] 35 | (let [query-result (volatile! {})] 36 | (maybe-sys sys 37 | (binding [system/parser-external-value (fn [index] 38 | (get external-values index)) 39 | system/query-output (fn [[dyna-code line-number] result-info] 40 | (let [ctx (:context result-info) 41 | qv (make-variable "$query_result_var") 42 | val (if (is-bound-in-context? qv ctx) 43 | (get-value-in-context qv ctx) 44 | (:rexpr result-info))] 45 | (vswap! query-result assoc line-number val)))] 46 | ;; any queries made when evaluating the string are going to be passed to the query-output function 47 | (eval-string query) 48 | ;; convert the results of the different queries to a java Object array 49 | (into-array Object (for [k (sort (keys @query-result))] 50 | (get @query-result k))))))) 51 | 52 | (defn make-rexpr [name args] ;; name is a string, and args should be an array of arguments which are passed into the R-expr 53 | (apply construct-rexpr name args)) 54 | 55 | (defn create-system [] 56 | (system/make-new-dyna-system)) 57 | 58 | ;; this might want to have some assumption which gets reset during every query, 59 | ;; such that would allow for an external function to change and for the results 60 | ;; to not get memoized outside of the scope of a single query 61 | (defn define-external-function [sys ^String name arity ^ExternalFunction func] 62 | (maybe-sys sys 63 | (assert (and (string? name) (int? arity))) 64 | (let [vars (vec (for [i (range arity)] 65 | (make-variable (str "$" i)))) 66 | out (make-variable (str "$" arity)) 67 | ff (fn [& args] 68 | (.call func (into-array Object args))) 69 | rexpr (make-function-call ff out vars)] 70 | 71 | (add-to-user-term! current-dir DynaTerm/null_term name arity rexpr) 72 | (eval-ast (make-term ("$compiler_expression" ("make_system_term" ("/" name arity)))))))) 73 | -------------------------------------------------------------------------------- /src/java/dyna/fastmap/FastFlatMap.java: -------------------------------------------------------------------------------- 1 | package dyna.fastmap; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.HashMap; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * A hash map which will generate custom classes to quickly 11 | * 12 | * Not used currently 13 | */ 14 | public class FastFlatMap { 15 | private FastFlatMap() { 16 | } 17 | 18 | 19 | static abstract class MapInternal { 20 | public abstract MapInternal set(Object key, Object val); 21 | 22 | public abstract Object get(Object key); 23 | 24 | public abstract int num_entries(); 25 | 26 | public abstract Object get_key(int i); // these could use unsafe internally which would allow it to treat the object like an array 27 | 28 | public abstract Object get_value(int i); 29 | } 30 | 31 | static abstract class MapExtendNewElement { 32 | public abstract MapInternal set_new(MapInternal existing_map, Object key, Object value); 33 | } 34 | 35 | /** 36 | * Thrown when the reference pointer needs to be updated for which donwstream classes are going to be generated as a result 37 | */ 38 | static final class MapExtendNewChangeRef extends RuntimeException { 39 | @Override 40 | public Throwable fillInStackTrace() { return null; } 41 | public final MapExtendNewElement new_ref; 42 | public final MapInternal map_result; 43 | 44 | public MapExtendNewChangeRef(MapExtendNewElement a, MapInternal b) { 45 | new_ref = a; 46 | map_result = b; 47 | } 48 | } 49 | 50 | public static final class Map { 51 | private MapInternal m; 52 | Map(MapInternal x) { m = x; } 53 | public void set(Object key, Object val) { 54 | m = m.set(key, val); // this might return a new internal map, or it might return the same value 55 | } 56 | public Object get(Object key) { 57 | return m.get(key); 58 | } 59 | public int size() { 60 | final MapInternal l = m; 61 | int r = 0; 62 | final int ents = l.num_entries(); 63 | for(int i = 0; i < ents; i++) 64 | if(l.get_value(i) != null) 65 | r++; 66 | return r; 67 | } 68 | 69 | public boolean empty() { 70 | final MapInternal l = m; 71 | final int ent = l.num_entries(); 72 | for(int i = 0; i < ent; i++) 73 | if(l.get_value(i) != null) 74 | return false; 75 | return true; 76 | } 77 | } 78 | 79 | private static boolean can_use_pointer_equality(Object key) { 80 | // look at the class of this and return true if we can use pointer equality without having to call .equals 81 | // this will make it faster 82 | return false; 83 | } 84 | 85 | 86 | static private final Object static_key_lock = new Object(); 87 | static private final HashMap static_keys = new HashMap<>(); 88 | static private final AtomicLong static_key_counter = new AtomicLong(); 89 | 90 | static Object _get_static_key(long k) { 91 | synchronized(static_key_lock) { 92 | Object r = static_keys.get(k); 93 | static_keys.remove(k); 94 | return r; 95 | } 96 | } 97 | 98 | final static sun.misc.Unsafe theUnsafe; 99 | 100 | static { 101 | sun.misc.Unsafe v = null; 102 | try { 103 | Field f = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); 104 | f.setAccessible(true); 105 | v = (sun.misc.Unsafe) f.get(null); 106 | } 107 | catch (NoSuchFieldException e) {} 108 | catch (IllegalArgumentException e) {} 109 | catch (IllegalAccessException e) {} 110 | theUnsafe = v; 111 | } 112 | 113 | 114 | } 115 | -------------------------------------------------------------------------------- /test/dyna/efficient_rexpr_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.efficient-rexpr-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.utils :refer [debug-repl]]) 4 | (:require [dyna.core]) 5 | (:require [dyna.base-protocols :refer [is-empty-rexpr? is-non-empty-rexpr? get-value-in-context]]) 6 | (:require [dyna.rexpr :refer [make-disjunct make-multiplicity make-unify make-variable make-constant make-conjunct make-proj-many make-aggregator simplify]]) 7 | (:require [dyna.rexpr-builtins :refer [make-times make-range]]) 8 | (:require [dyna.rexpr-disjunction :refer [is-disjunct-op?]]) 9 | (:require [dyna.rexpr-aggregators-optimized :refer [is-aggregator-op-outer?]]) 10 | (:require [dyna.system :as system]) 11 | (:require [dyna.context :as context]) 12 | (:require [dyna.simple-test :refer [run-string str-test]])) 13 | 14 | (defmacro run-optimized-rexprs [& body] 15 | `(do 16 | (alter-var-root #'system/use-optimized-rexprs (constantly true)) 17 | ~@body)) 18 | 19 | (deftest make-disjunct1 20 | (run-optimized-rexprs 21 | (run-string " 22 | f(1) = 2. 23 | f(2) = 3. 24 | f(3) = 4. 25 | 26 | assert f(1) = 2. 27 | "))) 28 | 29 | (deftest make-disjunct2 30 | (run-optimized-rexprs 31 | (run-string " 32 | f(X) += 1. 33 | f(X) += X. 34 | 35 | r(X) = X. 36 | 37 | assert r(f(5)) = 6. 38 | "))) 39 | 40 | 41 | (deftest make-disjunct3 42 | (run-optimized-rexprs 43 | (run-string " 44 | f(0,0) = 1. 45 | f(0,1) = 2. 46 | f(1,0) = 3. 47 | f(1,1) = 4. 48 | 49 | a(X,Y) += f(X,Z) * f(Z,Y). 50 | 51 | c += f(X,Y). 52 | 53 | %print c. 54 | assert c = 10. 55 | 56 | %print a(0,0). 57 | 58 | assert a(0,0) = 1 + 2*3. 59 | assert a(1,0) = 3 + 4*3. 60 | 61 | b += a(X,0). 62 | 63 | assert b = 22. %1 + 2*3 + 3 + 4*3. 64 | %print b. 65 | "))) 66 | 67 | 68 | (deftest disjunct-op-empty 69 | (run-optimized-rexprs 70 | (let [r (make-disjunct [(make-multiplicity 1) (make-unify (make-variable 'a) (make-constant 1))])] 71 | (is (is-disjunct-op? r)) 72 | (is (is-non-empty-rexpr? r)) 73 | (is (not (is-empty-rexpr? r)))))) 74 | 75 | 76 | (deftest efficient-aggregator1 77 | (run-optimized-rexprs 78 | (let [r (make-aggregator "+=" (make-variable 'result) (make-variable 'incoming) true 79 | (make-disjunct [(make-proj-many [(make-variable 'A)] (make-conjunct [(make-range (make-constant 0) (make-constant 10) (make-constant 1) (make-variable 'A) (make-constant true)) 80 | (make-times (make-variable 'A) (make-constant 7) (make-variable 'incoming))])) 81 | 82 | (make-proj-many [(make-variable 'A)] (make-conjunct [(make-range (make-constant 20) (make-constant 30) (make-constant 1) (make-variable 'A) (make-constant true)) 83 | (make-times (make-variable 'A) (make-constant 13) (make-variable 'incoming))])) 84 | ]))] 85 | (is (is-aggregator-op-outer? r)) 86 | (let [ctx (context/make-empty-context r) 87 | res (context/bind-context-raw ctx (simplify r))] 88 | (is (= (get-value-in-context (make-variable 'result) ctx) (+ (* (reduce + (range 0 10)) 7) 89 | (* (reduce + (range 20 30)) 13)))))))) 90 | 91 | (deftest efficient-aggregator2 92 | (run-optimized-rexprs 93 | (run-string " 94 | f(X) += V:range(X)*10. 95 | f(X) += V:range(X,100)*50. 96 | 97 | assert f(17) == 242060. 98 | "))) 99 | 100 | (deftest efficient-aggregator3 101 | (run-optimized-rexprs 102 | (run-string " 103 | 104 | % define a basic matrix 105 | a(0,0) = 1. 106 | a(0,1) = 2. 107 | a(1,0) = 0. 108 | a(1,1) = 1. 109 | 110 | b(X,Y) += a(X,Z) * a(Z,Y). 111 | 112 | print b(0,0). 113 | "))) 114 | 115 | 116 | (deftest efficient-aggregator4 117 | (run-optimized-rexprs 118 | (run-string " 119 | sum([]) = 0. 120 | sum([X|Y]) = X+sum(Y). 121 | 122 | %print sum([1,2,3,4]). 123 | 124 | assert sum([1,2,3,4]) = 10. 125 | ")) 126 | ) 127 | -------------------------------------------------------------------------------- /src/resources/dyna/prelude_theory.dyna: -------------------------------------------------------------------------------- 1 | % this file is loaded at the start of the system 2 | 3 | 4 | :- export key/1. 5 | 6 | %:- macro key/1. % declare that the key expression is a macro. This will be something that should take the AST for something, but what is the name that is going to be used by this expression then? Are we just going to quote escape its arguments. Are we going to give it the entire AST for its arguments including 7 | 8 | :- dispose key(quote1). 9 | :- make_system_term key/1. 10 | key(QuotedName) = $reflect(QuotedName, Dynabase, Name, _), 11 | $get_key_for_expression(Dynabase, Name, QuotedName). 12 | 13 | % the macros for a given arity of a function will 14 | %% key(AST) = $reflect(AST, Dynabase, Name, Args), 15 | %% &$get_key_for_expression(Dynabase, Name, Args). 16 | 17 | % want to be able to do something like key(foo(X)) and then this will instead get 18 | 19 | 20 | :- export list/1. 21 | :- make_system_term list/1. 22 | % for annotation that somethign is a list. This will match against all list types 23 | list([]). 24 | list([_|A]) :- list(A). 25 | 26 | :- export list/2. 27 | :- dispose list(quote1, eval). 28 | :- make_system_term list/2. 29 | % allows for type annotation against something. This 30 | list(Type, []). 31 | list(Type, [X|A]) :- *Type(X), list(Type, A). 32 | 33 | 34 | %% $eval_list([]) = []. 35 | %% $eval_list([X|A]) = [$eval(X)|$eval_list(A)]. 36 | 37 | 38 | :- export list_length/1. 39 | :- make_system_term list_length/1. 40 | list_length([]) = 0. 41 | list_length([X|A]) = list_length(A) + 1. 42 | 43 | 44 | :- export append/3. 45 | :- make_system_term append/3. 46 | append([], A, A). 47 | append([X|Y], A, [X|B]) :- append(Y, A, B). 48 | 49 | map_list([], F) = []. 50 | map_list([X|A], Function) = [*Function(X)|map_list(A, Function)]. 51 | 52 | % do remap the values in a map, it would require that there is some cut 53 | % operator, such that it does not rebind the balues multiple times? I suppose 54 | % that we could say that it non-deterministically pulls a value of the map, 55 | % though maybe it should just pull the first value from the iterator? So it 56 | % would be deterministic? 57 | % 58 | % but then that would have to be some sort of "delayed" binding to some 59 | % operation, but that makes the computation more complicated. It would also 60 | % need to declare that there is only one way in which a variable could be bound. 61 | % I suppose that it could do the binding via iteration, which would mean that it 62 | % must be trying to loop over the domain of a variable? 63 | % 64 | % maybe there needs to be some $map_first_key() which can read out the first key 65 | % in the map, so it would be able to unpack stuff 66 | 67 | map_dict({}, Function) = {}. 68 | map_dict(Map, Function) = 69 | Key=$map_first_key(Map), % will be deterministic wrt to the Map, and provide some binding for Key 70 | Map={Key -> Value|Rest}, % this could have higher multiplicity for a given Value, so this would be a problem, or multiple values come out of the expression 71 | [NKey,NVal] = *Function(Key,Val), 72 | {NKey -> NVal | map_dict(Rest, Function)}. 73 | 74 | % this unpacking of some map value should only remove one entry from the map, and its multiplicty returned should _never_ be greater than one 75 | % otherwise something like the above function will not work 76 | % additionaly, it should not automattically pull the value of the map 77 | % I suppose that the map_first_key entry might also want to allow for the differen tkeys to get pulled out of the map? 78 | 79 | % if this just returns the value once, then that is essentially making some 80 | % version of cut I suppose, but this could also make that kind of a cut using 81 | % one of the aggregators? It would just have to represent the expression 82 | 83 | :- export in_list/2. 84 | :- make_system_term in_list/2. 85 | in_list([X|_], X). 86 | in_list([_|Xs], X) :- in_list(Xs, X). 87 | 88 | :- export $with_key/2. 89 | :- make_system_term $with_key/2. 90 | $with_key(Value,Key) = &$with_key(Value,Key). 91 | 92 | 93 | 94 | 95 | % would be nice if we could continue to allow aggregators to be defined in dyna 96 | % code. something like it would construct some macro or something so that the 97 | % aggregator -= would just be an alias for += where a unitary minus is added 98 | % into the expression 99 | 100 | :- define_aggregator "-=". 101 | 'aggregator_-='(&$define_term(Head, DBase, "-=", Body)) = &$define_term(Head, DBase, "+=" &$unary_-(Boddy)). 102 | 'aggregator_-=_combine'(A,B) = 123. 103 | 104 | :- make_system_term $union_type/3. 105 | $union_type(A, B, Value) |= *A(Value). 106 | $union_type(A, B, Value) |= *B(Value). 107 | -------------------------------------------------------------------------------- /src/java/dyna/ThreadVar.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import clojure.lang.IFn; 4 | import clojure.lang.AFn; 5 | import clojure.lang.RT; 6 | import clojure.lang.Atom; 7 | import clojure.lang.PersistentHashSet; 8 | 9 | 10 | /** 11 | * These are the thread local variables which are tracked/used by a dyna program 12 | * 13 | * This replaces clojure's ^:dynamic using helpers in utils.clj as the 14 | * implementation of ^:dynamic is very slow, and indirects through a hash map 15 | * every time that it access a thread local variable. 16 | * 17 | * The context is also a thread local variable, but it is handled directly 18 | * through the ContextHandle class 19 | */ 20 | @SuppressWarnings("unchecked") 21 | public final class ThreadVar { 22 | 23 | private ThreadVar() {} 24 | 25 | private static final ThreadLocal handle = new ThreadLocal() { 26 | @Override 27 | protected ThreadVar initialValue() { 28 | return new ThreadVar(); 29 | } 30 | }; 31 | public static ThreadVar get() { return handle.get(); } 32 | 33 | 34 | // assumption.clj 35 | public Object current_watcher = null; 36 | public boolean fast_fail_on_invalid_assumption = false; 37 | 38 | // rexpr.clj 39 | public Rexpr current_top_level_rexpr = null; 40 | public Object current_simplify_stack = null; 41 | public IFn current_simplify_running = null; 42 | 43 | public IFn memoization_make_guesses_handler = new AFn() { 44 | public Object invoke(Object memo_table, Object variables, Object variable_values) { 45 | return false; 46 | } 47 | }; 48 | public boolean simplify_with_inferences = false; 49 | public boolean simplify_looking_for_fast_fail_only = false; 50 | 51 | public boolean expand_user_calls = true; 52 | 53 | // memoization_v2.clj 54 | public boolean expand_memoization_placeholder = true; 55 | public boolean lookup_memoized_values = true; 56 | public Object memoization_forward_placeholder_bindings = null; 57 | 58 | // rexpr_builtins.clj 59 | public boolean dollar_free_matches_ground_values = false; 60 | 61 | // rexpr_aggregators_optimized.clj 62 | public IFn aggregator_op_contribute_value = RT.var("dyna.rexpr-aggregators-optimized", "aggregator-op-contribute-value-default"); 63 | 64 | public IFn aggregator_op_additional_constraints = RT.var("dyna.rexpr-aggregators-optimized", "aggregator-op-additional-constraints-default"); 65 | 66 | 67 | public IFn aggregator_op_saturated = new AFn () { 68 | public Object invoke(){ 69 | return false; 70 | } 71 | }; 72 | 73 | public IFn aggregator_op_get_variable_value = RT.var("dyna.base-protocols", "get-value"); 74 | 75 | public boolean aggregator_op_should_eager_run_iterators = false; 76 | 77 | // public Object aggregator_active_aggregator = null; 78 | // public Object aggregator_accumulator = null; 79 | // public Object aggregator_ 80 | 81 | 82 | // system.clj 83 | public boolean auto_run_agenda_before_query = Boolean.parseBoolean(System.getProperty("dyna.auto_run_agenda", "true")); 84 | //public boolean use_optimized_rexprs = Boolean.parseBoolean(System.getProperty("dyna.optimized_rexprs", "true")); 85 | 86 | public static boolean jit_default_state = Boolean.parseBoolean(System.getProperty("dyna.enable_jit", "false")); 87 | 88 | public boolean use_optimized_disjunct = true; 89 | public boolean generate_new_jit_rewrites = jit_default_state; 90 | public boolean generate_new_jit_states = jit_default_state; 91 | public boolean recursive_transformation_to_jit_state = true; 92 | public boolean is_generating_jit_rewrite = false; 93 | 94 | public Atom globally_defined_user_term = new Atom(RT.map()); 95 | public Atom user_defined_terms = new Atom(RT.map()); 96 | public Atom user_exported_terms = new Atom(RT.map()); 97 | public Atom imported_files = new Atom(PersistentHashSet.EMPTY); 98 | public DynaAgenda work_agenda = new DynaAgenda(); 99 | public Atom user_recursion_limit = new Atom(default_recursion_limit); 100 | public static final int default_recursion_limit = Integer.valueOf(System.getProperty("dyna.recursion_limit", "20")); 101 | public IFn query_output = RT.var("clojure.core", "println"); 102 | public IFn parser_external_value = new AFn() { 103 | public Object invoke(Object index) { 104 | throw new DynaUserError("No external value handler set"); 105 | } 106 | }; 107 | public Atom dynabase_metadata = new Atom(RT.map()); 108 | public Object dyna_active_system = null; 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/clojure/notused_dyna/gradient_numbers.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.gradient-numbers 2 | (:require [dyna.utils :refer :all]) 3 | (:import (dyna TracedNumber))) 4 | 5 | (defn make-number [^double x] 6 | (TracedNumber. x 0 (fn [z queue] nil))) 7 | 8 | (defn add-to-gradient [num queue grad] 9 | (if (instance? TracedNumber num) 10 | (do (.addToGradient ^TracedNumber num grad) 11 | (.enqueue ^TracedNumber num queue)))) 12 | 13 | (defn- get-value [num] 14 | (if (instance? TracedNumber num) 15 | (.value ^TracedNumber num) 16 | num)) 17 | 18 | (defn- get-height 19 | ([] 0) 20 | ([num] 21 | (if (instance? TracedNumber num) 22 | (+ 1 (.height ^TracedNumber num)) 23 | 0)) 24 | ([a & other] 25 | (max (get-height a) (get-height other)))) 26 | 27 | 28 | (defmethod print-method TracedNumber [^TracedNumber this ^java.io.Writer w] 29 | (.write w (.toString this))) 30 | 31 | ; 32 | ;(defn grad-add [a b] 33 | ; (TracedNumber. (+ (get-value a) (get-value b)) 34 | ; (get-height a b) 35 | ; (fn [z queue] 36 | ; (add-to-gradient a queue (.getGradient z)) 37 | ; (add-to-gradient b queue (.getGradient z)) 38 | ; ))) 39 | ; 40 | ;(defn grad-mul [a b] 41 | ; (TracedNumber. (* (get-value a) (get-value b)) 42 | ; (get-height a b) 43 | ; (fn [z queue] 44 | ; (add-to-gradient a queue (* (get-value b) (.getGradient z))) 45 | ; (add-to-gradient b queue (* (get-value a) (.getGradient z)))))) 46 | 47 | (defn- add-get-values [params val] 48 | (if (contains? params val) 49 | `(get-value ~val) 50 | (map-same-type (partial add-get-values params) val))) 51 | 52 | 53 | (defmacro defn-gradient-op [op params forward backwards] 54 | (let [all-args (set params) 55 | forward-mapped (add-get-values all-args forward) 56 | ;backward-mapped (add-get-values all-args backwards) 57 | ] 58 | `(defn ~op ~params 59 | (if (or ~@(for [p params] `(instance? TracedNumber ~p))) 60 | (TracedNumber. ~forward-mapped 61 | (get-height ~@params) 62 | (fn ~'[^TracedNumber gradv queue] 63 | (let [~'grad (.getGradient ~'gradv)] 64 | ~backwards))) 65 | 66 | ~forward))) 67 | ) 68 | 69 | 70 | 71 | 72 | (defn-gradient-op grad-add [a b] 73 | (+ a b) 74 | (do (add-to-gradient a queue grad) 75 | (add-to-gradient b queue grad))) 76 | 77 | (defn-gradient-op grad-mul [a b] 78 | (* a b) 79 | (do (add-to-gradient a queue (* grad (get-value b))) 80 | (add-to-gradient b queue (* grad (get-value a))))) 81 | 82 | (defn-gradient-op grad-div [a b] 83 | (/ a b) 84 | (do (add-to-gradient a queue (/ grad (get-value b))) 85 | (add-to-gradient b queue (- (* grad (get-value a) (/ 1 (* (get-value b) (get-value b)))))))) 86 | 87 | (defn-gradient-op grad-sub [a b] 88 | (- a b) 89 | (do (add-to-gradient a queue grad) 90 | (add-to-gradient b queue (- grad)))) 91 | 92 | (defn-gradient-op grad-sin [x] 93 | (Math/sin x) 94 | (add-to-gradient x queue (Math/cos grad))) 95 | 96 | (defn-gradient-op grad-cos [x] 97 | (Math/cos x) 98 | (add-to-gradient x queue (- (Math/sin grad)))) 99 | 100 | (defn-gradient-op grad-tan [x] 101 | (Math/tan x) 102 | (add-to-gradient x queue (let [z (Math/cos grad)] (/ 1 (* z z))))) 103 | 104 | (defn-gradient-op grad-exp [x] 105 | (Math/exp x) 106 | (add-to-gradient x queue (* grad (Math/exp (get-value x))))) 107 | 108 | 109 | (defn-gradient-op grad-abs [x] 110 | (if (< x 0) (- x) x) 111 | (add-to-gradient x queue (* grad (if (< x 0) -1 1)))) 112 | 113 | (defn-gradient-op grad-pow [x e] 114 | (Math/pow x e) 115 | (do (add-to-gradient x queue (* grad e (Math/pow x (- e 1)))) 116 | (add-to-gradient e queue 99999999))) 117 | 118 | (defn grad-max [a b] 119 | (if (> (get-value a) (get-value b)) 120 | a b)) 121 | 122 | (defn grad-min [a b] 123 | (if (> (get-value a) (get-value b)) 124 | b a)) 125 | 126 | ;; operations which do not directly influence the numerical values, do not need gradients, as those will 127 | ;; change the shape of the graph. So this would have that there are some of which 128 | 129 | (defn set-loss-compute-grad [num] 130 | (if (instance? TracedNumber num) 131 | (TracedNumber/runBackprop num))) 132 | -------------------------------------------------------------------------------- /dyna_python_module/README.md: -------------------------------------------------------------------------------- 1 | # Python Module for Dyna 2 | 3 | This is a Python wrapper for interacting with the Dyna runtime (which is written 4 | in Clojure). This library exposes the most commonly used functionallity and 5 | should be sufficient for most users of Dyna. Additional access to the Dyna 6 | internals can be access by writing raw Clojure code. 7 | 8 | ## Installing 9 | 10 | Either Download the prebuilt dyna binary or build the binary using the following steps: 11 | ```bash 12 | git clone https://github.com/argolab/dyna3.git 13 | cd dyna3 14 | make 15 | ls ./dyna-standalone-0.1.0 # the resulting prebuilt binary 16 | ``` 17 | 18 | To install Dyna into Python do: 19 | ```bash 20 | source ~/path/to/python/virtual-env/bin/activate # OPTIONAL activate your python virtual environment 21 | 22 | ./dyna-standalone-0.1.0 install-python # Install Dyna into the current python environment 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```python 28 | from dyna import Dyna 29 | 30 | inst = Dyna() # make a new Dyna runtime 31 | 32 | inst.run(''' 33 | print "hello world from Dyna". 34 | ''') 35 | ``` 36 | 37 | ### Defining Rules 38 | 39 | Any rule can be defined by passing a string of Dyna code to the `Dyna.run()` 40 | method. Statements in Dyna in end with a period. For example: 41 | 42 | ```python 43 | inst.run(''' 44 | foo = 123. % define the term foo to take the value 123 45 | 46 | list_length([]) = 0. 47 | list_length([X|Y]) = 1 + list_length(Y). 48 | ''') 49 | 50 | inst.run_file('path_to_file.dyna') 51 | ``` 52 | 53 | ### Queries 54 | 55 | Dyna programs can be queried (much like a database) and have a result returned. 56 | This is done by using the `Dyna.query()` method. Queries end with a question 57 | mark. Multiple queries can be done at the same time and all of their results 58 | will be returned in a list. For example: 59 | 60 | ```python 61 | result = inst.query(''' 62 | 1 + 2? 63 | list_length([1,2,3,4])? 64 | ''') 65 | 66 | assert result[0] == 1 + 2 67 | assert result[1] == len([1,2,3,4]) 68 | ``` 69 | 70 | ### Parameter Arguments 71 | 72 | Both `Dyna.run()` and `Dyna.query()` can take "query parameters" which are 73 | subsuited into the parsed code. This is akin to writing a parameterized 74 | statement when interacting with a SQL database. The values are represented 75 | using `$0`, `$1`, . . . `$n` where the number corresponds with the positional 76 | argument. For example: 77 | 78 | ```python 79 | result = inst.query(''' 80 | 1 + $0 ? 81 | str("hello ", $1) ? 82 | ''', 3, "world") 83 | 84 | assert result[0] == 4 85 | assert result[1] == "hello world" 86 | ``` 87 | 88 | The values passed as parameters can either be primitive types which 89 | can be interpreted by dyna (numbers, strings, dyna terms) or opaque types 90 | (e.g. a pytorch tensor) which can be passed around without Dyna directly 91 | interacting with the value. 92 | 93 | ```python 94 | class OpaqueType: pass 95 | opaque = OpaqueType() 96 | inst.run(''' 97 | ref = $0. % save a reference to the opaque value into the dyna database 98 | ''', opaque) 99 | 100 | result = inst.query(''' 101 | ref ? % get the reference back from dyna 102 | ''') 103 | 104 | assert result[0] is opaque 105 | ``` 106 | 107 | 108 | ### External Functions 109 | 110 | The Python program can also export functions to the Dyna runtime. These 111 | functions can perform computation using python methods as well as interact with 112 | any value which is opaque to Dyna. These methods are defined using the 113 | `Dyna.define_function()` decorator on a function. For example: 114 | 115 | ```python 116 | @inst.define_function() 117 | def my_func(a, b): 118 | return a + 7*b 119 | 120 | @inst.define_function('function_name') 121 | def x(y): return y 122 | 123 | inst.run(''' 124 | assert my_func(3, 6) == 3 + 6*7. 125 | assert function_name(3) == 3. 126 | ''') 127 | ``` 128 | 129 | Note: Any function exposed to Dyna should be _functional_. A function might get 130 | called multiple times on the same arguments, or the order in which the function 131 | is called may differ from what has been written in the program. 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | This will wrap the Dyna runtime and make calls into the runtime from Python. 142 | 143 | Features that we should support: 144 | * Loading a string of dyna code 145 | * Loading a dyna file 146 | * making queries against the dyna program and getting back a primitive value (float,int,string) 147 | * Some structured Term representation for fully ground terms. E.g. `&foo(1,2,3)` should get returned somehow 148 | * Lists should automattically get converted into Python lists 149 | 150 | 151 | * Passing opaque python values through the dyna runtime 152 | * calling Python functions from dyna. Though there will be no guarantee that 153 | those functions will get called once, or if they are not functional, then that 154 | could be an issue 155 | -------------------------------------------------------------------------------- /test/dyna/benchmark_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.benchmark-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.core]) 4 | (:require [dyna.rexpr :refer [simplify simplify-fully null-term simplify-top find-iterators]]) 5 | (:require [dyna.utils :refer :all]) 6 | (:require [dyna.rexpr-jit-v2 :refer :all]) 7 | (:require [dyna.base-protocols :refer :all]) 8 | (:require [dyna.rexpr-constructors :refer [convert-to-jitted-rexpr make-aggregator make-variable make-constant make-range make-multiplicity]]) 9 | (:require [dyna.context :as context]) 10 | (:require [dyna.system :as system]) 11 | (:require [clojure.data.csv :as csv]) 12 | (:require [clojure.java.io :as io]) 13 | (:require [clojure.string :as string]) 14 | (:import [dyna StatusCounters])) 15 | 16 | (defn- synthize-rexpr [r] 17 | (tbinding [generate-new-jit-states true] 18 | (convert-to-jitted-rexpr r))) 19 | 20 | 21 | (def benchmark-rexpr 22 | ;; this is equivalent to f(X) += N:range(0,X). 23 | (make-aggregator "+=" (make-variable "result") (make-variable "incoming") true (make-range (make-constant 0) 24 | (make-variable "X") 25 | (make-constant 1) 26 | (make-variable "incoming") 27 | (make-constant true)))) 28 | 29 | (defn f-clojure [X] 30 | (reduce + (range X))) 31 | 32 | (defn f-closed-form [X] 33 | (/ (* (- X 1) X) 2)) 34 | 35 | (defn run-benchmark [func] 36 | (for [N (range 10 500 5) 37 | ;;(range 10 10000 10) 38 | ] 39 | (do (println N) 40 | (let [start-matches-attempted (StatusCounters/get_matches_attempted) 41 | start-rewrites-performed (StatusCounters/get_rewrites_performed) 42 | start-rexpr-created (StatusCounters/get_rexpr_created) 43 | start (. System nanoTime)] 44 | (dotimes [i 10] 45 | (func N)) 46 | (let [end (. System nanoTime)] 47 | [N (- end start) 48 | (- (StatusCounters/get_matches_attempted) start-matches-attempted) 49 | (- (StatusCounters/get_rewrites_performed) start-rewrites-performed) 50 | (- (StatusCounters/get_rexpr_created) start-rexpr-created) 51 | ]))))) 52 | 53 | (deftest basic-rewrite-bench 54 | (let [rexpr benchmark-rexpr 55 | br (run-benchmark 56 | (fn [to-value] 57 | (let [ctx (context/make-empty-context rexpr)] 58 | (ctx-set-value! ctx (make-variable "X") to-value) 59 | (context/bind-context-raw 60 | ctx 61 | (let [res (simplify-fully rexpr)] 62 | (is (= res (make-multiplicity 1))) 63 | (is (= (ctx-get-value ctx (make-variable "result")) (f-closed-form to-value)))))))) 64 | ] 65 | (with-open [writer (io/writer "/tmp/basic-rewrite-bench-1.csv")] 66 | (csv/write-csv writer (cons ["problem size" "time in ns" "matches attempted" "rewrites performed" "rexpr created"] 67 | br))))) 68 | 69 | (deftest basic-clojure-bench 70 | (let [br (run-benchmark 71 | (fn [to-value] 72 | (f-clojure to-value)))] 73 | (with-open [writer (io/writer "/tmp/basic-clojure-bench-1.csv")] 74 | (csv/write-csv writer (cons ["problem size" "time in ns" "matches attempted" "rewrites performed" "rexpr created"] 75 | br))))) 76 | 77 | (deftest basic-jit-bench 78 | ;; (println "test started " (-> (java.lang.management.ManagementFactory/getRuntimeMXBean) 79 | ;; (.getName) 80 | ;; (string/split #"@") 81 | ;; (first))) 82 | ;; (Thread/sleep 5000) 83 | (tbinding 84 | [generate-new-jit-rewrites true] 85 | (let [rexpr (synthize-rexpr benchmark-rexpr) 86 | br (run-benchmark 87 | (fn [to-value] 88 | (let [ctx (context/make-empty-context rexpr)] 89 | (ctx-set-value! ctx (make-variable "X") to-value) 90 | (context/bind-context-raw 91 | ctx 92 | (let [res (simplify-fully rexpr)] 93 | (is (= res (make-multiplicity 1))) 94 | (is (= (ctx-get-value ctx (make-variable "result")) (f-closed-form to-value)))))))) 95 | ] 96 | (with-open [writer (io/writer "/tmp/jit-rewrite-bench-1.csv")] 97 | (csv/write-csv writer (cons ["problem size" "time in ns" "matches attempted" "rewrites performed" "rexpr created"] 98 | br)))))) 99 | 100 | (deftest basic-rewriting 101 | (let [rexpr benchmark-rexpr 102 | to-value 10000 103 | ctx (context/make-empty-context rexpr)] 104 | (ctx-set-value! ctx (make-variable "X") to-value) 105 | (context/bind-context-raw 106 | ctx 107 | (let [res (simplify-fully rexpr)] 108 | (is (= res (make-multiplicity 1))) 109 | ;(debug-repl) 110 | (is (= (ctx-get-value ctx (make-variable "result")) (f-closed-form to-value))) 111 | )))) 112 | -------------------------------------------------------------------------------- /test/dyna/jit_test_v1.clj: -------------------------------------------------------------------------------- 1 | (comment 2 | (ns dyna.jit-test 3 | (:require [clojure.test :refer :all]) 4 | (:require [dyna.core]) 5 | (:require [dyna.rexpr :refer [simplify]]) 6 | (:require [dyna.utils :refer :all]) 7 | (:require [dyna.rexpr-constructors :refer :all]) 8 | (:require [dyna.rexpr-jit :refer :all]) 9 | (:require [dyna.base-protocols :refer :all]) 10 | (:require [dyna.context :as context])) 11 | 12 | 13 | #_(deftest basic-jit 14 | (let [rexpr (make-unify (make-variable 'a) (make-variable 'b)) 15 | rr (get-rexpr-arg-map rexpr)] 16 | ; (debug-repl) 17 | )) 18 | 19 | (deftest basic-jit2 20 | (let [rexpr (make-conjunct [(make-add (make-variable 'a) (make-variable 'b) (make-variable 'c)) 21 | (make-times (make-variable 'c) (make-constant 7) (make-variable 'd))]) 22 | [synth-rexpr _](synthize-rexpr rexpr)] 23 | (is (not (nil? synth-rexpr))))) 24 | 25 | (deftest basic-jit3 26 | (let [rexpr (make-proj (make-variable 'c) ;; d = (a + 1)*7 27 | (make-conjunct [(make-add (make-variable 'a) (make-constant 1) (make-variable 'c)) 28 | (make-times (make-variable 'c) (make-constant 7) (make-variable 'd))])) 29 | [jit-rexpr jit-type] (synthize-rexpr rexpr)] 30 | ;; make a new rewrite rule 31 | (synthize-rewrite-rule jit-rexpr 32 | :arg-matches {(make-variable 'a) [:ground] 33 | (make-variable 'd) [:free]} ;; a list of which pattern matches should be considered as matching 34 | :example-values {;(make-variable 'a) 22 ;; an "example" value which can be used to generate code for the rewrite, but not to condition specifically on this value 35 | } 36 | :values {;(make-variable 'a) 22 ;; make a rewrite which is _specific_ to this particular value 37 | }) 38 | 39 | (let [rr (make-conjunct [(make-unify (make-variable 'a) (make-constant 11)) 40 | jit-rexpr]) 41 | ctx (context/make-empty-context rr) 42 | res (context/bind-context-raw ctx (simplify rr))] 43 | ;(debug-repl "res from jit") ;; this should have that the 44 | (is (= res (make-multiplicity 1))) 45 | (is (= (ctx-get-value ctx (make-variable 'd)) (* (+ 11 1) 7)))))) 46 | 47 | (deftest basic-jit4 48 | (let [rexpr (make-proj-many [(make-variable 'c) (make-variable 'e)] 49 | ;; a + b + d + f = g 50 | (make-conjunct [(make-add (make-variable 'a) (make-variable 'b) (make-variable 'c)) 51 | (make-add (make-variable 'c) (make-variable 'd) (make-variable 'e)) 52 | (make-add (make-variable 'e) (make-variable 'f) (make-variable 'g))])) 53 | [jit-rexpr jit-type] (synthize-rexpr rexpr)] 54 | (synthize-rewrite-rule jit-rexpr 55 | :arg-matches {(make-variable 'a) [:ground] 56 | (make-variable 'b) [:ground] 57 | (make-variable 'd) [:ground] 58 | (make-variable 'f) [:free] 59 | (make-variable 'g) [:free]}) 60 | (let [rr (make-conjunct [(make-unify (make-variable 'a) (make-constant 1)) 61 | (make-unify (make-variable 'b) (make-constant 2)) 62 | (make-unify (make-variable 'd) (make-constant 3)) 63 | jit-rexpr]) 64 | ctx (context/make-empty-context rr) 65 | res (context/bind-context-raw ctx (simplify rr))] 66 | ;(debug-repl "res from jit") 67 | (is (= (make-add (make-constant 6) (make-variable 'f) (make-variable 'g)) res))))) 68 | 69 | (deftest basic-jit5 70 | (let [rexpr (make-proj-many [(make-variable 'c) (make-variable 'e)] 71 | ;; a + b + d + f = g 72 | (make-conjunct [(make-add (make-variable 'a) (make-variable 'b) (make-variable 'c)) 73 | (make-add (make-variable 'c) (make-variable 'd) (make-variable 'e)) 74 | (make-add (make-variable 'e) (make-variable 'f) (make-variable 'g))])) 75 | [jit-rexpr jit-type] (synthize-rexpr rexpr)] 76 | (synthize-rewrite-rule jit-rexpr 77 | :arg-matches {(make-variable 'a) [:ground] 78 | (make-variable 'b) [:ground] 79 | (make-variable 'd) [:free] 80 | (make-variable 'f) [:free] 81 | (make-variable 'g) [:free]}) 82 | (let [rr (make-conjunct [(make-unify (make-variable 'a) (make-constant 1)) 83 | (make-unify (make-variable 'b) (make-constant 2)) 84 | jit-rexpr]) 85 | ctx (context/make-empty-context rr) 86 | res (context/bind-context-raw ctx (simplify rr))] 87 | ;(debug-repl "res from jit") 88 | (is (some #{(make-constant 3)} (get-arguments res))) ;; check one of the arguments represents the result of the computation 89 | (is (.startsWith (str (rexpr-name res)) "jit-rexpr"))))) 90 | ) 91 | -------------------------------------------------------------------------------- /test/dyna/trie_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.trie-test 2 | (:require [clojure.test :refer :all]) 3 | (:require [dyna.core]) 4 | (:require [dyna.utils :refer :all]) 5 | (:require [dyna.prefix-trie :refer :all]) 6 | (:import [dyna.prefix_trie IPrefixTrie PrefixTrie]) 7 | (:import [dyna ClojureUnorderedVector ClojureHashMap])) 8 | 9 | 10 | 11 | 12 | (deftest small-trie 13 | (let [trie (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1]) 14 | :c (ClojureUnorderedVector/create [2 3])})})) 15 | values (trie-get-values trie nil) 16 | values-1a (into #{} (trie-get-values trie [:a nil])) 17 | values-1c (into #{} (trie-get-values trie [:c nil])) 18 | values-2b (into #{} (trie-get-values trie [nil :b]))] 19 | (is (= values-1a #{[(list :a :c) 3] [(list :a :c) 2] [(list :a :b) 1]})) 20 | (is (= values-1c #{})) 21 | (is (= values-2b #{[(list :a :b) 1]})) 22 | (is (instance? clojure.lang.LazySeq values)))) 23 | 24 | (deftest update-trie 25 | (let [trie (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1]) 26 | :c (ClojureUnorderedVector/create [2 3])})})) 27 | trie2 (assoc trie [:foo :bar] 99) 28 | 29 | values (into #{} (trie-get-values trie2 nil))] 30 | (is (= values #{[(list :a :c) 3] [(list :foo :bar) 99] [(list :a :c) 2] [(list :a :b) 1]})))) 31 | 32 | (deftest nil-val-trie 33 | (let [trie (make-PrefixTrie 2 0x1 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1]) 34 | :c (ClojureUnorderedVector/create [2 3])}) 35 | nil (ClojureHashMap/create {:b (ClojureUnorderedVector/create [4]) 36 | :d (ClojureUnorderedVector/create [5])})})) 37 | values (into #{} (trie-get-values trie nil)) 38 | values-a1 (into #{} (trie-get-values trie [:a nil])) 39 | values-b2 (into #{} (trie-get-values trie [nil :b]))] 40 | (is (= values #{[(list nil :b) 4] [(list nil :d) 5] [(list :a :c) 3] [(list :a :c) 2] [(list :a :b) 1]})) 41 | (is (= values-a1 #{[(list nil :b) 4] [(list nil :d) 5] [(list :a :c) 3] [(list :a :c) 2] [(list :a :b) 1]})) 42 | (is (= values-b2 #{[(list nil :b) 4] [(list :a :b) 1]})))) 43 | 44 | (deftest merge-tries 45 | (let [trie1 (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1]) 46 | :c (ClojureUnorderedVector/create [2 3])})})) 47 | trie2 (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [3]) 48 | :d (ClojureUnorderedVector/create [4])})})) 49 | trie (trie-merge trie1 trie2) 50 | values (into #{} (trie-get-values trie nil))] 51 | (is (= values #{[(list :a :d) 4] [(list :a :b) 3] [(list :a :c) 3] [(list :a :c) 2] [(list :a :b) 1]})))) 52 | 53 | 54 | ;; want the order as well as the "type" at the leaf to not matter wrt 55 | (deftest hash-trie-different-order 56 | (let [trie1 (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1 2 3])})})) 57 | trie2 (make-PrefixTrie 2 0 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create (list 3 1 2))})}))] 58 | (assert (= (hash trie1) (hash trie2))) 59 | (assert (= trie1 trie2)) 60 | )) 61 | 62 | 63 | (deftest trie-map-values-test 64 | (let [trie1 (make-PrefixTrie 2 0x1 (ClojureHashMap/create {:a (ClojureHashMap/create {:b (ClojureUnorderedVector/create [1]) 65 | :c (ClojureUnorderedVector/create [2 3])}) 66 | nil (ClojureHashMap/create {:b (ClojureUnorderedVector/create [4]) 67 | :d (ClojureUnorderedVector/create [5])})})) 68 | trie2 (trie-map-values trie1 nil (fn [a b] (* 2 b)))] 69 | (is (= (.root trie2) 70 | {:a {:b [2] 71 | :c [4 6]} 72 | nil {:b [8] 73 | :d [10]}})))) 74 | 75 | 76 | (deftest unordered-vector-test 77 | (let [v (ClojureUnorderedVector/create [1 2 3 4]) 78 | v2 (ClojureUnorderedVector/create [3 4 2 1]) 79 | v3 (into ClojureUnorderedVector/EMPTY (range 100))] 80 | (assert (= 3 (get v 2))) 81 | (assert (= v v2)) 82 | (assert (= 100 (count v3))) 83 | (assert (= 1655435961 (hash v3))) ;; the hash code should be stable 84 | )) 85 | 86 | (deftest new-hash-map-test 87 | (let [v ClojureHashMap/EMPTY 88 | v2 (assoc v :a :b) 89 | v3 (conj v2 (zipmap (range 20) (map #(+ 1 %) (range)))) 90 | v4 (vec v3) 91 | v5 (into {} v3) 92 | v6 (into v v5)] 93 | (assert (= :b (get v2 :a))) 94 | (assert (= 21 (count v3))) 95 | (assert (= 8 (get v3 7))) 96 | (assert (= 21 (count v4))) 97 | (assert (every? #(some #{[% (+ 1 %)]} v4) (range 20))) 98 | (assert (= 1932828094 (hash v3))) ;; the hash should be stable 99 | (assert (= v3 v5)) 100 | (assert (= v3 v6)) 101 | (assert (= v5 v3)) 102 | (assert (= v5 v6)) 103 | )) 104 | -------------------------------------------------------------------------------- /src/java/dyna/DynaAgenda.java: -------------------------------------------------------------------------------- 1 | package dyna; 2 | 3 | import java.util.PriorityQueue; 4 | import java.util.HashSet; 5 | 6 | public class DynaAgenda { 7 | 8 | private final PriorityQueue queue = new PriorityQueue<>(100, IDynaAgendaWork.comparator); // the work on the agenda 9 | private final HashSet queued_work = new HashSet(); // the work 10 | private long work_processed = 0; 11 | private long time_processing = 0; 12 | 13 | public DynaAgenda() {} 14 | 15 | public synchronized void push_work(IDynaAgendaWork work) { 16 | // if we add the same work multiple times, then we are going to ignore it 17 | if(!queued_work.contains(work)) { 18 | queue.add(work); 19 | queued_work.add(work); 20 | } 21 | } 22 | 23 | public void process_agenda() throws InterruptedException { 24 | if(queue.isEmpty()) 25 | return; 26 | if(print_agenda_running) 27 | System.err.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Running agenda~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); 28 | long local_processed = 0; 29 | long agenda_start_processing = System.currentTimeMillis(); 30 | try { 31 | while(true) { 32 | if(Thread.interrupted()) 33 | throw new InterruptedException(); 34 | //System.out.println("Running work"); 35 | IDynaAgendaWork work; 36 | synchronized (this) { 37 | work = queue.poll(); 38 | if(work == null) break; 39 | queued_work.remove(work); 40 | } 41 | work.run(); 42 | local_processed++; 43 | if(print_progress && local_processed % 5173 == 0) { 44 | System.err.print("\rAgenda status, work processed: "+local_processed); 45 | } 46 | } 47 | } finally { 48 | work_processed += local_processed; 49 | long time = System.currentTimeMillis() - agenda_start_processing; 50 | StatusCounters.agenda_processing_time(local_processed, time); 51 | if(print_agenda_running) 52 | System.err.println("~~~~~~~~~~~~~~~~Done running agenda, " + local_processed + " work processed in " + time/1000.0 + " seconds~~~~~~~~~~~~~"); 53 | } 54 | } 55 | 56 | /* 57 | // This is theory code about how a concurrent system could be implement 58 | 59 | public final int num_threads = System.getProperty("dyna.num_threads") != null ? Integer.parseInt(System.getProperty("dyna.num_threads")) : Runtime.getRuntime().availableProcessors(); 60 | 61 | private final ReentrantReadWriteLock concurrent_work_lock = new ReentrantReadWriteLock(); 62 | private final rwlExecutorService thread_pool = Executors.newCachedThreadPool(); 63 | private void process_agenda_concurrently() throws InterruptedException { 64 | for(int i = 0; i < num_threads; i++) 65 | // submit the work into the 66 | } 67 | 68 | private void run_agenda_from_thread() { 69 | // this would have to manage the clojure variable dynamic values. Maybe do something like get the dynamic values from the initial thread 70 | // and then push those values on all of the worker threads 71 | 72 | while(!is_interrupted) { 73 | IDynaAgendaWork work; 74 | synchronized (this) { 75 | work = queue.poll(); 76 | if(work == null) break; 77 | queued_work.remove(work); 78 | } 79 | // the main idea is that the work could tell us if it is safe to run concurrently. The memoization values should be able to do that 80 | // without too much trouble as long as it checks that the relevant values are not modified inbetween the start and finishing of processing 81 | // other work (such as making new memo tables or other management tasks) would just run single threaded as they would be required to grab 82 | // the write lock which would block until everything else is taken out 83 | Lock lock = work.can_run_concurrently() ? concurrent_work_lock.readLock() : concurrent_work_lock.writeLock(); 84 | lock.lock(); 85 | try { 86 | work.run(); 87 | } catch (DynaRetryWork err) { 88 | // this would put the work back on the agenda 89 | } finally { 90 | lock.unlock(); 91 | } 92 | local_processed++; // this could be replaced with an atomic counter 93 | if(print_progress && local_processed % 5173 == 0) { 94 | System.err.print("\rAgenda status, work processed: "+local_processed); 95 | } 96 | } 97 | } 98 | 99 | 100 | void interrupt() { 101 | is_interrupted = true; // something which stops it from processing, rather than using thread.interrupted 102 | } 103 | 104 | */ 105 | 106 | 107 | public boolean is_done() { 108 | return queue.isEmpty(); 109 | } 110 | 111 | public synchronized void clear_agenda() { 112 | // to be only called from the REPL by the user. This will potentially cause things to break 113 | queue.clear(); 114 | queued_work.clear(); 115 | } 116 | 117 | public static final boolean print_progress = Boolean.parseBoolean(System.getProperty("dyna.print_agenda_progress", "false")); 118 | public static final boolean print_agenda_running = Boolean.parseBoolean(System.getProperty("dyna.print_agenda_running", "false")); 119 | public static final boolean print_agenda_work = Boolean.parseBoolean(System.getProperty("dyna.print_agenda_work", "false")); 120 | 121 | } 122 | -------------------------------------------------------------------------------- /test/dyna/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.core-test 2 | (:require [clojure.test :refer :all] 3 | [dyna.core :refer :all] 4 | [dyna.base-protocols :refer :all] 5 | [dyna.rexpr :refer :all] 6 | [dyna.context :as context] 7 | [dyna.system :as system] 8 | [dyna.rexpr-builtins :refer :all] 9 | [dyna.utils :refer [debug-repl make-term match-term defsimpleinterface]])) 10 | 11 | 12 | (defmacro run-unoptimized-rexprs [& body] 13 | `(try 14 | (do 15 | (alter-var-root #'system/use-optimized-rexprs (constantly false)) 16 | ~@body) 17 | (finally 18 | (alter-var-root #'system/use-optimized-rexprs (constantly true))))) 19 | 20 | (deftest basic-rexpr 21 | (let [rexpr (make-add (make-constant 2) (make-constant 3) (make-constant 5)) 22 | ctx (context/make-empty-context rexpr) 23 | r2 (context/bind-context ctx (simplify rexpr)) 24 | r3 (make-multiplicity 1)] 25 | (is (= r2 r3)))) 26 | 27 | 28 | (deftest basic-rexpr2 29 | (let [rexpr (make-add (make-constant 2) (make-constant 3) (make-variable 'var1)) 30 | ctx (context/make-empty-context rexpr) 31 | r2 (context/bind-context-raw ctx (simplify rexpr)) 32 | r3 (make-multiplicity 1)] 33 | ;r3 (make-unify (make-variable 'var1) (make-constant 5)) 34 | (is (= r2 r3)) 35 | (is (= 5 (ctx-get-value ctx (make-variable 'var1)))) 36 | (println ctx))) 37 | 38 | 39 | 40 | (deftest basic-conjunct 41 | (let [rexpr (make-conjunct 42 | [(make-add (make-constant 2) (make-variable 'v1) (make-variable 'v2)) 43 | (make-add (make-constant 2) (make-constant 3) (make-variable 'v1))]) 44 | ctx (context/make-empty-context rexpr) 45 | r2 (context/bind-context-raw ctx (simplify-fully rexpr)) 46 | r3 (make-multiplicity 1)] 47 | (is (= r2 r3)) 48 | (is (= (ctx-get-value ctx (make-variable 'v1)) 5)) 49 | (is (= (ctx-get-value ctx (make-variable 'v2)) 7)))) 50 | 51 | 52 | (deftest basic-disjunct 53 | (run-unoptimized-rexprs 54 | (let [rexpr (make-disjunct 55 | [(make-unify (make-variable 'foo) (make-constant 123)) 56 | (make-unify (make-variable 'foo) (make-constant 123))]) 57 | ctx (context/make-empty-context rexpr) 58 | r2 (context/bind-context-raw ctx (simplify-fully rexpr)) 59 | r3 (make-multiplicity 2)] 60 | (is (= r2 r3)) 61 | (is (= (ctx-get-value ctx (make-variable 'foo)) 123))))) 62 | 63 | (deftest basic-disjunct2 64 | ;; these can not be combined because the values of the variables are different 65 | (run-unoptimized-rexprs ;; the disjunct will get replaced with the optimized disjunct if this is set to true (the default) 66 | (let [rexpr (make-disjunct 67 | [(make-unify (make-variable 'foo) (make-constant 123)) 68 | (make-unify (make-variable 'foo) (make-constant 456))]) 69 | ctx (context/make-empty-context rexpr) 70 | r2 (context/bind-context-raw ctx (simplify-fully rexpr))] 71 | (is (= rexpr r2)) 72 | (is (not (is-bound-in-context? (make-variable 'foo) ctx)))))) 73 | 74 | (deftest basic-aggregator1 75 | (let [rexpr (make-aggregator "+=" 76 | (make-variable 'out) 77 | (make-variable 'agg-in) 78 | true 79 | (make-unify (make-variable 'agg-in) (make-constant 777))) 80 | ctx (context/make-empty-context rexpr) 81 | r2 (context/bind-context-raw ctx (simplify-fully rexpr))] 82 | (is (= (make-multiplicity 1) r2)) 83 | (is (= (ctx-get-value ctx (make-variable 'out)) 777)))) 84 | 85 | (deftest basic-aggregator2 86 | (let [rexpr (make-aggregator "+=" 87 | (make-variable 'out) 88 | (make-variable 'agg-in) 89 | true 90 | (make-conjunct [(make-multiplicity 2) 91 | (make-unify (make-variable 'agg-in) 92 | (make-constant 333))])) 93 | ctx (context/make-empty-context rexpr) 94 | r2 (context/bind-context-raw ctx (simplify-fully rexpr))] 95 | (is (= (make-multiplicity 1) r2)) 96 | (is (= (ctx-get-value ctx (make-variable 'out)) 666)))) 97 | 98 | (deftest basic-aggregator3 99 | (run-unoptimized-rexprs 100 | (let [rexpr (make-aggregator "+=" 101 | (make-variable 'out) 102 | (make-variable 'agg-in) 103 | true 104 | (make-disjunct 105 | [(make-unify (make-variable 'agg-in) (make-constant 111)) 106 | (make-unify (make-variable 'agg-in) (make-constant 666))])) 107 | ctx (context/make-empty-context rexpr) 108 | r2 (context/bind-context-raw ctx (simplify-fully rexpr))] 109 | (is (= (make-multiplicity 1) r2)) 110 | (is (= (ctx-get-value ctx (make-variable 'out)) 777))))) 111 | 112 | 113 | (deftest make-match-term 114 | (let [term (make-term ("myterm" ("another" 1 2 3))) 115 | cnt (atom 0)] 116 | (dyna.utils/match-term term ("myterm" ("another" a b c)) 117 | (assert (= a 1)) 118 | (swap! cnt inc)) 119 | (assert (= 1 @cnt)))) 120 | 121 | 122 | (deftest match-rexpr-test 123 | (let [rexpr (make-unify (make-variable 'a) (make-variable 'b)) 124 | cnt (atom 0)] 125 | (match-rexpr rexpr (unify (:variable avar) (:variable bvar)) 126 | (swap! cnt inc) 127 | (assert (= avar (make-variable 'a)))) 128 | 129 | (is (= @cnt 1)))) 130 | 131 | 132 | (defsimpleinterface my-simple-interface 133 | (my-simple-interface-fn1 [a b c]) 134 | (my-simple-interface-fn2 [a d])) 135 | 136 | (deftype my-simple-type [v] 137 | my-simple-interface 138 | (my-simple-interface-fn1 [this a b c] 139 | (+ a (* b 5) (* c 7))) 140 | (my-simple-interface-fn2 [this a d] 141 | (+ a (* d 5) (* v 7)))) 142 | 143 | (deftest simple-interface 144 | (let [val (my-simple-type. 17) 145 | r1 (my-simple-interface-fn1 val 3 11 13) 146 | r2 (my-simple-interface-fn2 val 3 11)] 147 | (is (= r1 (+ 3 (* 11 5) (* 13 7)))) 148 | (is (= r2 (+ 3 (* 11 5) (* 17 7)))))) 149 | 150 | 151 | (def-base-rexpr no-arg-test-case []) 152 | -------------------------------------------------------------------------------- /src/resources/dyna/builtin_libraries/gradient_number.dyna: -------------------------------------------------------------------------------- 1 | 2 | 3 | gradient_add(A, B) = { 4 | value = A.value + B.value. 5 | %gradient += 0. % want the gradient to be null by default. That way it will not get instantated until something downstream causes it to get instantated 6 | }. 7 | 8 | all_gradient_numbers(&add(A,B)) = gradient_add(A,B). % track that this object has been created, and give it the name of &add 9 | 10 | all_gradient_numbers(N).gradient += gradient_add(N,_).gradient. % anytime that there is a gradient which has been added to "us", it gets propagated back into anything which we came from. Essentially, this is going to have to determine that there are some valus which correspond with the gradient information 11 | all_gradient_numbers(N).gradient += gradient_add(_,N).gradient. 12 | 13 | gradient_mul(A, B) = { 14 | value = A.value * B.value. 15 | %gradient += 0. 16 | }. 17 | 18 | 19 | % how efficient will this be when finding which of the expressions are going to match. This would require searching through all of the declared values. 20 | % also if there are gradient operations, then this would find which of the expressions. How will this handle which of the operations correspond 21 | all_gradient_numbers(&mul(A,B)) = gradient_mul(A,B). 22 | all_gradient_numbers(A).gradient += gradient_mul(A,B).gradient / B.value. 23 | all_gradient_numbers(B).gradient += gradient_mul(A,B).gradient / A.value. 24 | 25 | 26 | % if someone sets a loss on one of the numbers, then that is going to propagate into all of the gradient information 27 | 28 | X.gradient += wrapped(X).loss. 29 | 30 | 31 | wrapper(X) = { 32 | % wrapper adds the methods such that the above declaritive operaitons can identify which of the objects are going to be created as a result of the computiton 33 | % 34 | value = X.value. 35 | wrapped = X. 36 | gradient += 0; X.gradient. % the gradient is always zero unless something else is defined for it 37 | 38 | 39 | % something like once the add operation runs, it can call with ggf, for 40 | % ground-ground-free. If that isn't defined, then this would just null out, so 41 | % it is the same result in the end anyways. Which dynabase is called on could 42 | % be non-deterministic, so something like just one of the ground dynabases might 43 | % want the dynabsae to somehow mark that it allows for the operator overloading, 44 | % otherwise this will probably just be better as an error? 45 | % 46 | % Ideally, there would be some approach to dispatching the types of an 47 | % expression, but that would require knowing which of the arguments are going to 48 | % be ground and in what order, if we are passing in that information to which 49 | % function gets called. 50 | % 51 | % The alternate is to just do something like `add(A,B,C) :- foo.`, but then that 52 | % means that the idea of something that looks like a function behaves like a 53 | % function is broken as those operations could now return multiple values for 54 | % the return result. 55 | % 56 | % Also, at least in the case of dispatching to the calls of some expression, this would require 57 | 58 | 59 | operator_add_ggf(A,B) = wrapper(gradient_add(A.wrapped, B.wrapped)). 60 | operator_add_gfg(A,B) = wrapper(gradient_sub(A.wrapped, B.wrapped)). 61 | operator_add_gff(A,B) = wrapper(gradient_sub(A.wrapped, B.wrapped)). 62 | 63 | operator_times_ggf(A,B) = A*B. 64 | 65 | operator_min_ggf(A,B) min= A;B. 66 | 67 | operator_max_ggf(A,B) min= A;B. 68 | 69 | operator_pow_ggf(A,B) = A ** B. 70 | 71 | operator_exp_gf(A) = exp(A). 72 | 73 | operator_lessthan_ggf(A,B) = A < B. 74 | 75 | operator_lessthaneq_ggf(A,B) = A <= B. 76 | 77 | operator_equals_ggf(A,B) = A == B. 78 | 79 | operator_notequals_ggf(A,B) = A != B. 80 | 81 | operator_land_ggf(A,B) = A & B. 82 | 83 | operator_lor_ggf(A, B) = A | B. 84 | 85 | operator_not_gf(A) = not(A). 86 | 87 | operator_sin_gf(A) = sin(A). 88 | 89 | operator_cos_gf(A) = cos(A). 90 | 91 | operator_tan_gf(A) = tan(A). 92 | 93 | operator_sinh_gf(A) = sin(A). 94 | 95 | operator_cosh_gf(A) = cos(A). 96 | 97 | operator_tanh_gf(A) = tan(A). 98 | 99 | }. 100 | 101 | % the loss expression might want to be some macro? In which case it would want to expand this expression. But then how would it work in the case 102 | loss.wrapped.gradient += 1. 103 | 104 | 105 | $loss(AST) = &$dynabase_call(&$dynabase_call(AST, &wrapped), &gradient). 106 | :- macro $loss/1. 107 | 108 | %$loss(Node) += 1. This is somehow defined in another module. So I suppose 109 | % that this would become something that is made "global". Thus the expressions 110 | % can somehow still be defined for it? But if there are expressions with this 111 | % taking which of the operations would correspond with the different operations 112 | 113 | % I suppose that the loss could be something that the gradient is taken WRT, so 114 | % that it would be the parameter to some gradient method, but then that would 115 | % mean that it is performing a lot of "calls" which are not going to be lazy as a result 116 | 117 | $parameter(Name) = wrapper { 118 | value = 0. 119 | %gradient += 0. 120 | }. 121 | 122 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 123 | 124 | 125 | gradient_number(P) = { 126 | add(A,B,C) :- C = A + B. % this would still not let this known which of the expressions are ground. I suppose that we would have to have some impure grounding expression or something. 127 | 128 | add(A,B,C) :- $impure_know_grounding(&add(A,B,C)). 129 | 130 | % this isn't declaritive either, as it requires that make_add only has the values which are actually insantiated. Though if we asusmpe that the gradient value would be zero by default, then this might be "ok". In which case, it would only require which values are currently present as most of the values would just be the identity 131 | gradient += make_add(Self, _).gradient. 132 | gradient += make_add(_, Self).gradient. 133 | }. 134 | 135 | % if this knows the grounding of the expressions, then it could represent which of the expressions .... 136 | add("++-", A, B, C) :- C = A+B. % then this would have to come back to the gradient number. The value of B&C would have to somehow know that it 137 | add("+-+", 138 | 139 | make_add(A, B) = { 140 | %% A.gradient += Self.gradient. % this is not possible, as this allows for non-declaritive operations to be performed as a result. 141 | %% B.gradient += Self.gradient. 142 | value = A.value + B.value; 143 | }. 144 | 145 | 146 | 147 | $pramater(Name) = { 148 | value += 0. 149 | gradient += 0. 150 | }. 151 | -------------------------------------------------------------------------------- /src/clojure/dyna/base_protocols.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.base-protocols 2 | (:require [dyna.utils :refer [import-interface-methods defsimpleinterface]]) 3 | (:import [dyna DIterable DIterator DIteratorInstance Dynabase 4 | Rexpr RContext RexprValue 5 | ])) 6 | 7 | ;; this file defines the base protocols (interfaces) which are referenced 8 | ;; throughout the rest of the project in different files. The implementations 9 | ;; for these methods are scattered around 10 | 11 | (import-interface-methods Rexpr) 12 | #_(defprotocol Rexpr 13 | (primitive-rexpr [this]) ;; return a primitive r-expr for the expression 14 | ;;(replace-expressions [this expressions-map]) ;; replace a given nested expression 15 | (get-variables [this]) ;; get the variables from the current rexpr 16 | (get-children [this]) 17 | (get-argument [this n]) 18 | (get-argument-name [this name]) 19 | (get-arguments [this]) 20 | (as-list [this]) ; use for printing out the structure 21 | 22 | (exposed-variables [this]) ; return variables values that are exposed externally (so hide aggregators and proj) 23 | (get-all-variables-rec [this]) 24 | 25 | ;; these functions can recursivy walk the R-expr and rewmap the different variables which appear 26 | ;; if there is something that 27 | 28 | 29 | (remap-variables [this variable-renaming-map]) 30 | (rewrite-rexpr-children [this remap-function]) 31 | (rewrite-rexpr-children-no-simp [this remap-function]) 32 | (remap-variables-func [this remap-function]) 33 | 34 | ;; in the case of hidden variables, those re something which need to get added to the map when it recurses through 35 | (remap-variables-handle-hidden [this variable-renaming-map]) 36 | 37 | ;; meaning that the multiplicity of this expression will be at most 1. allows 38 | ;; for other additional rewrites to be applied to this expression 39 | (is-constraint? [this]) 40 | 41 | ;; return the name of this type of R-expr as a string, can be used by external 42 | ;; code. internally it is more efficient to use match or is-X? for matching 43 | ;; against the type of R-expr 44 | (rexpr-name [this]) 45 | 46 | (is-empty-rexpr? [this]) ;; if the multiplicity of this expression is zero 47 | (is-non-empty-rexpr? [this]) ;; if there is some branch of the disjunct which is non-zero 48 | 49 | ;; return a map of the internal values which are stored, and it . 50 | ;(get-arguments-map [this]) 51 | 52 | (rexpr-map-function-with-access-path [this cb-fn]) 53 | 54 | (all-conjunctive-rexprs [this] 55 | ;; return [set-of-projected-out-variables, The rexpr] 56 | ) 57 | 58 | (rexpr-jit-info [this]) 59 | 60 | 61 | ;; An R-expr will have to hit a basecase as R-exprs do not allow for "infinite" 62 | ;; expressions. So if there is something without a basecase, then that means 63 | ;; that it would be impossible for it to resolve as anything 64 | ;; Returns: 65 | ;; 0 indicates that for sure there is no base case 66 | ;; 1 this uses indirect calls somewhere so we can not be sure if this will have a base cases as a result of the indirection 67 | ;; 2 there is some recursion, but there is also a base case that could maybe get hit 68 | ;; 3 there is no recursion 69 | (check-rexpr-basecases [this stack]) 70 | 71 | 72 | (simplify-fast-rexprl [this simplify]) 73 | (simplify-inference-rexprl [this simplify])) 74 | 75 | ;; (visit-rexpr-children [this remap-function]) ;; this will visit any nested R-exprs on the expression, and return a new expression of the same type with 76 | ;(visit-all-children [this remap-function]) ;; this will visit 77 | 78 | 79 | 80 | (import-interface-methods RexprValue) 81 | #_(defprotocol RexprValue 82 | (get-value [this]) 83 | (get-value-in-context [this ctx]) 84 | (get-representation-in-context [this ctx]) 85 | (set-value! [this value]) 86 | (is-bound? [this]) 87 | (is-bound-in-context? [this ctx]) 88 | (all-variables [this])) 89 | 90 | 91 | (import-interface-methods RContext) 92 | #_(defprotocol RContext 93 | (ctx-get-rexpr [this]) 94 | (ctx-get-value [this variable]) 95 | (ctx-is-bound? [this variable]) 96 | (ctx-set-value! [this variable value]) 97 | (ctx-add-rexpr! [this rexpr]) 98 | (ctx-add-context! [this other-context]) 99 | (ctx-all-rexprs [this]) 100 | (ctx-exit-context [this resulting-rexpr]) 101 | ;(construct-context-rexpr [this]) 102 | (ctx-intersect [this other]) 103 | (ctx-subtract [this other]) 104 | 105 | (ctx-scan-through-conjuncts [this scan-fn]) 106 | (ctx-contains-rexpr? [this rexpr]) 107 | 108 | ;; for debugging 109 | (ctx-get-inner-values [this]) 110 | (ctx-get-all-bindings [this]) 111 | 112 | (ctx-get-value-map-upto-context [this parent-context]) 113 | ) 114 | 115 | 116 | 117 | ;; (defrecord Dynabase [access-map] 118 | ;; Object 119 | ;; (toString [this] 120 | ;; (str "(Dynabase " access-map ")"))) 121 | 122 | (def undefined-dynabase (Dynabase. nil)) 123 | 124 | ;(import-interface-methods) 125 | 126 | 127 | (import-interface-methods DIterable) 128 | #_(defprotocol DIterable 129 | (iter-what-variables-bound [this]) 130 | (iter-variable-binding-order [this]) 131 | 132 | (iter-create-iterator [this which-binding])) 133 | 134 | (import-interface-methods DIterator) 135 | 136 | ;; should this also 137 | #_(defprotocol DIterator 138 | (iter-run-cb [this ^clojure.lang.IFn cb-fun]) ;; call back the function with the value 139 | (iter-run-iterable [this]) ;; return an iterable (possibly a lazy seq) for the different bindings 140 | ;;(iter-has-next [this]) 141 | (iter-bind-value [this value]) ;; return DIteratorInstance 142 | (iter-debug-which-variable-bound [this]) ;; which variable is bound by this iterator 143 | (iter-estimate-cardinality [this])) ;; estimate the number of values which will get bound by this iterator 144 | 145 | 146 | (import-interface-methods DIteratorInstance) 147 | #_(defprotocol DIteratorInstance 148 | (iter-variable-value [this]) ;; return Object which corresponds with the value from the iterator 149 | (iter-continuation [this])) ;; return an DIterator which is the next iterator 150 | 151 | 152 | (def iterator-empty-instance 153 | (reify DIterator 154 | (iter-run-cb [this cb-fun]) 155 | (iter-bind-value [this value] 156 | (throw (RuntimeException. "nothing to bind a value to"))) 157 | (iter-debug-which-variable-bound [this] nil))) 158 | 159 | 160 | ;; return the next iterator in the sequence of binding multiple values 161 | 162 | 163 | ;; (defrecord MemoizationContainer [RConditional 164 | ;; Rmemo 165 | ;; Rorig 166 | ;; assumption]) 167 | -------------------------------------------------------------------------------- /src/clojure/dyna/core.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.core 2 | (:require [aprint.core :refer [aprint]]) 3 | (:require [clojure.tools.namespace.repl :refer [refresh]]) 4 | ;(:require [clj-java-decompiler.core :refer [decompile]]) 5 | (:require [clojure.java.io :refer [resource file]]) 6 | 7 | (:require [dyna.system :as system]) 8 | (:require [dyna.utils :refer :all]) 9 | (:require [dyna.rexpr]) 10 | (:require [dyna.rexpr-constructors :refer :all]) 11 | (:require [dyna.base-protocols :refer :all]) 12 | (:require [dyna.context :as context]) 13 | (:require [dyna.rexpr-builtins]) 14 | (:require [dyna.rexpr-aggregators]) 15 | (:require [dyna.rexpr-dynabase]) 16 | (:require [dyna.rexpr-meta-programming]) 17 | ;(:require [dyna.memoization-v1]) 18 | (:require [dyna.rexpr-disjunction]) 19 | (:require [dyna.rexpr-aggregators-optimized]) 20 | (:require [dyna.memoization-v2]) ;; requires disjunct opt and aggregators opt 21 | 22 | ;; (:require [dyna.rexpr-jit]) 23 | (:require [dyna.ast-to-rexpr :refer [parse-string 24 | import-file-url 25 | eval-string 26 | eval-ast]]) 27 | (:require [dyna.repl :refer [repl pretty-print-query-result]]) 28 | (:require [dyna.rexpr-jit-v2]) 29 | (:import [dyna DynaTerm StatusCounters DynaUserError])) 30 | 31 | 32 | (defn init-system [] 33 | ;;(println "----------->>>>>>>>>>>>>>>>>>>>>>>>> init method on current system") 34 | (import-file-url (resource "dyna/prelude.dyna"))) 35 | 36 | (defn exit-system [] 37 | ;; there should be something for when the system is going to be destroyed. 38 | ;; this can run stuff like saving the files? 39 | ) 40 | 41 | 42 | (init-system) 43 | 44 | 45 | (defn real-main [args] 46 | ;(println (vec args)) 47 | 48 | ;; load the prelude file into the runtime before we start loading stuff 49 | ;;(import-file-url (resource "dyna/prelude.dyna")) 50 | 51 | (let [run-file (volatile! nil) 52 | other-args (transient [])] 53 | (loop [i 0] 54 | (when (< i (count args)) 55 | ;; the command line arguments can just be special shortcuts for commands that could have been run via the repl 56 | (case (get args i) 57 | "--import" (let [fname (get args (+ i 1))] 58 | (println "importing file " fname) 59 | ;; this is going to be a file, as we have already checked in the standalone-header 60 | (eval-ast (make-term ("$compiler_expression" ("import" fname)))) 61 | (recur (+ i 2))) 62 | "--csv-import" (let [term (get args (+ i 1)) 63 | file-name (get args (+ i 2)) 64 | [_ term-name term-arity] (re-matches #"(.+)/([0-9]+)" term)] 65 | (if (nil? term-name) 66 | (print "csv-import did not match the expected argument") 67 | (assert false)) 68 | (eval-ast (make-term ("$compiler_expression" ("import_csv" term-name (int term-arity) file-name)))) 69 | (recur (+ i 3))) 70 | "--csv-export" (let [term (get args (+ i 1)) 71 | file-name (get args (+ i 2)) 72 | [_ term-name term-arity] (re-matches #"(.+)/([0-9]+)" term)] 73 | (eval-ast (make-term ("$compiler_expression" ("export_csv" term-name (int term-arity) file-name)))) 74 | (recur (+ i 3))) 75 | "--run" (let [fname (get args (+ i 1))] 76 | (when-not (nil? @run-file) 77 | (println "the --run argument is specified more than once") 78 | (System/exit 1)) 79 | (vreset! run-file fname) 80 | (recur (+ i 2))) 81 | (do ;; just collect the other arguments into an array 82 | (conj! other-args (get args i)) 83 | (recur (+ i 1)))))) 84 | 85 | (let [other-args (persistent! other-args)] 86 | (if (or (not (empty? other-args)) (not (nil? @run-file))) 87 | (let [[run-file args] (if-not (nil? @run-file) 88 | [@run-file other-args] 89 | [(get other-args 0) (vec (drop 1 other-args))]) 90 | run-filef (file run-file)] 91 | (when-not (.isFile run-filef) 92 | (println "Unable to find file " run-file " to run") 93 | (System/exit 1)) 94 | (let [arg-list (DynaTerm/make_list args)] 95 | ;; define $command_line_args = ["arg1", "arg2", ..., "argN"] 96 | ;; and make it a global that can be access anywhere 97 | (eval-ast (make-term ("," 98 | ("$define_term" ("$command_line_args") DynaTerm/null_term "=" ("$constant" arg-list)) 99 | ("$compiler_expression" ("make_system_term" ("/" "$command_line_args" 0)))))) 100 | (when system/status-counters 101 | (StatusCounters/program_start)) 102 | (tbinding [system/query-output (fn [[query-text line-number] result] 103 | (if (Boolean/parseBoolean (System/getProperty "dyna.print_raw_query_results" "false")) 104 | (println query-text " " run-filef ":" line-number "\n" result) 105 | (pretty-print-query-result query-text result)))] 106 | (try 107 | (import-file-url (.toURL run-filef)) 108 | (catch DynaUserError err 109 | (do 110 | (print "\033[0;31mError Error Error\033[0m\n") 111 | (println (.getMessage err)) 112 | (System/exit 1))))) 113 | ;; TODO: this needs to handle the csv export functions 114 | (when system/status-counters 115 | (StatusCounters/print_counters)) 116 | (System/exit 0))) 117 | (do 118 | (eval-string "$command_line_args = []. 119 | :- make_system_term $command_line_args/0.") 120 | ;; then there are no arguments, so just start the repl9 121 | (repl) 122 | 123 | ;; TODO: is there some exception that can be caught in the case that the repl is getting quit, in which case, we should run the 124 | ;; csv save file operations at that point? 125 | 126 | (if system/status-counters 127 | (StatusCounters/print_counters)) 128 | ))))) 129 | 130 | (defn main [args] 131 | (try 132 | (real-main args) 133 | (finally (exit-system)))) 134 | 135 | 136 | 137 | ;; (defn main2 [& args] 138 | ;; (main (vec args))) 139 | 140 | (defn -main [] (main [])) 141 | -------------------------------------------------------------------------------- /src/clojure/dyna/assumptions.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.assumptions 2 | (:require [dyna.system :as system]) 3 | (:require [dyna.utils :refer :all]) 4 | (:import [java.util WeakHashMap]) 5 | (:import [dyna InvalidAssumption IDynaAgendaWork])) 6 | 7 | ;; any assumption which the current expression depends 8 | (def-tlocal current-watcher) 9 | (def-tlocal fast-fail-on-invalid-assumption) 10 | 11 | (declare depend-on-assumption 12 | make-assumption) 13 | 14 | 15 | (defsimpleinterface Watcher 16 | (notify-invalidated! [watching]) 17 | (notify-message! [watching message])) 18 | 19 | (defsimpleinterface Assumption 20 | (invalidate! []) 21 | (is-valid? []) 22 | (add-watcher! [^dyna.assumptions.Watcher watcher]) 23 | (send-message! [message])) 24 | 25 | 26 | (deftype assumption-no-message-cls 27 | [^WeakHashMap watchers 28 | ^Assumption upstream] 29 | Assumption 30 | (invalidate! [this] (invalidate! upstream)) 31 | (is-valid? [this] (is-valid? upstream)) 32 | (add-watcher! [this watcher] 33 | (assert (instance? Watcher watcher)) 34 | (locking watchers 35 | (.put watchers watcher nil) 36 | (when-not (is-valid? this) 37 | (notify-invalidated! watcher this) 38 | (when (tlocal fast-fail-on-invalid-assumption);*fast-fail-on-invalid-assumption* 39 | (throw (InvalidAssumption. "invalid assumption")))))) 40 | (send-message! [this message] 41 | (???)) 42 | 43 | Watcher 44 | (notify-invalidated! [this from-watcher] 45 | (locking watchers 46 | (doseq [w (.keySet watchers)] 47 | (notify-invalidated! w this)))) 48 | (notify-message! [this from-watcher message] 49 | ;; NOP 50 | ) 51 | Object 52 | (toString [this] (str "[Assumption-no-message isvalid=" (is-valid? this) 53 | " watchers=" (let [w watchers] 54 | (if-not (nil? w) 55 | (locking w) (str (.keySet w)))) 56 | " id=" (.hashCode this) 57 | "])")) 58 | (hashCode [this] (System/identityHashCode this)) 59 | (equals [this other] (identical? this other))) 60 | 61 | (deftype assumption 62 | [^WeakHashMap watchers 63 | valid] 64 | Assumption 65 | (invalidate! [this] 66 | (let [[old new] (swap-vals! valid (constantly false))] 67 | (when (= true old) ;; meaning that this was valid before 68 | (locking watchers 69 | (doseq [w (.keySet watchers)] 70 | (notify-invalidated! w this)) 71 | (.clear watchers))))) 72 | (is-valid? [this] @valid) 73 | (add-watcher! [this watcher] 74 | (locking watchers 75 | (if (is-valid? this) 76 | (.put watchers watcher nil) 77 | (do (notify-invalidated! watcher this) 78 | (when (tlocal fast-fail-on-invalid-assumption);*fast-fail-on-invalid-assumption* 79 | (throw (InvalidAssumption. "invalid assumption"))))))) 80 | 81 | (send-message! [this message] 82 | (locking watchers 83 | (doseq [w (.keySet watchers)] 84 | (notify-message! w this message)))) 85 | 86 | Watcher 87 | (notify-invalidated! [this from-watcher] (invalidate! this)) 88 | (notify-message! [this from-watcher message] (send-message! this (assoc message 89 | :from-upstream (conj (:from-upstream message ()) 90 | from-watcher)))) 91 | 92 | Object 93 | (toString [this] (str "[Assumption isvalid=" (is-valid? this) 94 | " watchers=" (let [w watchers] 95 | (if-not (nil? w) 96 | (locking w (str (.keySet w))))) 97 | " id=" (.hashCode this) 98 | "]")) 99 | (hashCode [this] (System/identityHashCode this)) 100 | (equals [this other] (identical? this other))) 101 | 102 | (defn assumption-no-messages [^Assumption x] 103 | (assert (instance? Assumption x)) 104 | (if (instance? assumption-no-message-cls x) 105 | x 106 | (let [r nil;(assumption-no-messages-cls. (WeakHashMap.) x) 107 | ] 108 | (add-watcher! x r) 109 | r))) 110 | 111 | (defn add-watcher-function! [^Assumption ass func] ;; if the variable name matches the class name, bad stuff happens...OMFG what in the world 112 | (let [f (reify Watcher 113 | (notify-message! [this assumpt message] (func message)) 114 | (notify-invalidated! [this assumpt] (func nil)))] 115 | (add-watcher! ass ^Watcher f))) 116 | 117 | (defmethod print-method assumption [^assumption this ^java.io.Writer w] 118 | (.write w (.toString this))) 119 | 120 | (defmethod print-dup assumption [^assumption this ^java.io.Writer w] 121 | ;; in the case that this is duplicated, this is going to have to start as 122 | ;; invalid, as we are going to have to recheck everything if it was to get reloaded 123 | (.write w "#=(dyna.assumptions/make-invalid-assumption)")) 124 | 125 | (defn make-assumption [] 126 | (assumption. (WeakHashMap.) ; downstream dependents 127 | (atom true) ; if this is still valid, this is atomic and we will only ever set it to false (no false->true) 128 | )) 129 | 130 | (defn make-invalid-assumption [] 131 | (assumption. nil (atom false))) 132 | 133 | 134 | ;; (comment 135 | ;; (defn make-reactive-value [initial-value] 136 | ;; (reactive-value. (atom [(make-assumption) 137 | ;; initial-value]) 138 | ;; (fn [] nil)))) 139 | 140 | (defn depend-on-assumption [^Assumption ass ;& {:keys [hard] :or {hard true}} 141 | ] 142 | ;; in this case, we are stating that the current computation would need to get 143 | ;; redone if the current assumption becomes invalid 144 | (let [w (tlocal current-watcher)] 145 | (when-not (nil? w) 146 | (add-watcher! ass w) 147 | ;; check the assumption after adding it might get invalidated inbetween 148 | (when (not (is-valid? ass)) 149 | ;; there should be some exception to restart the computation or something 150 | ;; it would allow for the runtime to check which of the expressions 151 | (throw (InvalidAssumption. "attempting to use invalid assumption")))))) 152 | 153 | (defmacro bind-assumption [assumpt & body] 154 | `(tbinding 155 | [current-watcher ~assumpt] 156 | ~@body)) 157 | 158 | (defmacro compute-with-assumption [& body] 159 | `(loop [assumpt# (make-assumption)] 160 | (let [[ok# res#] 161 | (tbinding 162 | [current-watcher assumpt# 163 | fast-fail-on-invalid-assumption true] 164 | #_(binding [;*current-watcher* assumpt# 165 | *fast-fail-on-invalid-assumption* true]) 166 | (try 167 | [true (do ~@body)] 168 | (catch ~InvalidAssumption err# 169 | [false false])))] 170 | (if ok# 171 | [assumpt# res#] 172 | (recur (make-assumption)))))) 173 | -------------------------------------------------------------------------------- /src/resources/dyna/prelude.dyna: -------------------------------------------------------------------------------- 1 | % :- export key/1. 2 | 3 | % :- dispose key(quote1). 4 | % :- make_system_term key/1. 5 | % key(QuotedName) = $reflect(QuotedName, Dynabase, Name, _), 6 | % $get_key_for_expression(Dynabase, Name, QuotedName). 7 | 8 | 9 | :- export list/1. 10 | 11 | % for annotation that something is a list. This will match against all list types 12 | list_rec([]). 13 | list_rec([_|A]) :- list_rec(A). 14 | 15 | % the "builtin" prelude is not allowed to be recursive, so this has to reference another function which is allowed to be recursive 16 | % if the make_system_term is recursive, then it will cause the startup processes to crash 17 | list(A) :- list_rec(A). 18 | :- make_system_term list/1. % set this after it is defined, as we can maybe want to enforce some error for this 19 | 20 | :- export list/2. 21 | :- dispose list(quote1, eval). % this is going to quote the type argument, but if we make quoting something that is already hapening, then could this have some issue? 22 | 23 | list_rec(Type, []). 24 | list_rec(Type, [X|A]) :- Type(X), list_rec(Type, A). 25 | list(Type, L) :- list_rec(Type, L). 26 | :- make_system_term list/2. 27 | 28 | :- export list_length/1. 29 | :- export list_length/2. 30 | list_length_rec([], 0). 31 | list_length_rec([_|X], R+1) :- list_length_rec(X, R). 32 | 33 | list_length(X) = list_length_rec(X, R), R. 34 | list_length(X, R) :- list_length_rec(X, R). 35 | :- make_system_term list_length/1. 36 | :- make_system_term list_length/2. 37 | 38 | 39 | :- export random/3. 40 | :- export random/1. 41 | :- export random/0. 42 | random(Key, Lower, Upper) = (builtin_random_int32(Key) + 2147483648) / 4294967296.0 * (Upper - Lower) + Lower. 43 | random(Key) = random(Key, 0, 1). 44 | 45 | :- macro random/0. 46 | random = `(random(*)). 47 | 48 | :- macro random/2. 49 | random(Lower, Upper) = `(random(*, `Lower, `Upper)). 50 | 51 | :- make_system_term random/0. 52 | :- make_system_term random/2. 53 | :- make_system_term random/1. 54 | :- make_system_term random/3. 55 | 56 | % return the current random seed used by the system 57 | $random_seed = $clojure'{ 58 | dyna.rexpr-builtins/random-seed 59 | }. 60 | :- make_system_term $random_seed/0. 61 | 62 | $null = &$null. 63 | :- make_system_term $null/0. 64 | 65 | % these are defined to return the quoted version, as int(X) will be calling a builtin thus we can write `X:list(int)` using these 66 | int = &int. 67 | float = &float. 68 | number = &number. 69 | string = &string. 70 | :- make_system_term int/0. 71 | :- make_system_term float/0. 72 | :- make_system_term number/0. 73 | :- make_system_term string/0. 74 | 75 | 76 | 77 | % this is the ?(foo(X)) operator right now. But there is no support for that in the parser atm 78 | % not sure if `?` should be given such a privileged position. Also using `?` as the end of a query 79 | :- macro is_defined/1. 80 | is_defined(X) = `((|= false ; true for _ is `X)). 81 | 82 | :- make_system_term is_defined/1. 83 | 84 | :- macro $term/0. 85 | $term = `(&$term(`(&$variable("$functor_name")), `(&$variable("$functor_arity")))). 86 | :- make_system_term $term/0. 87 | 88 | :- macro $filename/0. 89 | $filename = &$variable("$functor_filename"). 90 | :- make_system_term $filename/0. 91 | 92 | 93 | % this is generated by the parser when doing something like X:int|string => X:$union_type(&int,&string) => $union_type(&int,&string,X), X 94 | $union_type(A, B, Value) |= A(Value). 95 | $union_type(A, B, Value) |= B(Value). 96 | :- make_system_term $union_type/3. 97 | 98 | % this isn't going to work, as this might indirect through multiple calls before it gets to final call. In which case these extra variable names are going to get lost... 99 | % I Suppose that there could be some extra variable like $meta or $call_from which could track this additional information? Though not sure what the value would be. In most cases 100 | % it would probably just have that it would be null or not contain anything useful. 101 | 102 | % :- macro $caller_file/0. 103 | % :- macro $caller_name/0. 104 | % :- macro $caller_arity/0. 105 | % :- macro $caller_variables/0. 106 | % $caller_file = &$variable("$1000000"). % these values should match in ast_to_rexpr.clj 107 | % $caller_name = &$variable("$1000001"). 108 | % $caller_arity = &$variable("$1000002"). 109 | % $caller_variables = &$variable("$1000003"). 110 | % :- make_system_term $caller_file/0. 111 | % :- make_system_term $caller_name/0. 112 | % :- make_system_term $caller_arity/0. 113 | % :- make_system_term $caller_variables/0. 114 | 115 | :- macro $error/1. 116 | %$error(Msg) = `(&$error(str(`(&$constant(str($caller_file, ": ", $caller_name, "/", $caller_arity, " "))), `Msg))). 117 | $error(Msg) = `(&$error(`Msg)). 118 | :- make_system_term $error/1. 119 | 120 | 121 | 122 | $get_key(X) := &$error("with_key not found on expression"). 123 | $get_key(&$with_key(R, K)) := K. 124 | 125 | :- macro $arg/1. 126 | $arg(X) := `($get_key($call_without_with_key(`X))). 127 | $arg(&$variable(_)) := &$syntax_error("$arg can not be used on a variable, it must be used on the call directly, e.g. $arg(foo(1,2,X))"). 128 | $arg(&$constant(_)) := &$syntax_error("$arg can not be used on a constant"). 129 | :- make_system_term $arg/1. 130 | 131 | 132 | % is is possible that this could get used somewhere that $with_key isn't present (e.g. if with_key was used on a dynabase which shared the name with something else). 133 | % In this case, we have to handle that something might not unify with this expression. 134 | $value(X) := X. 135 | $value(&$with_key(R, K)) := R. 136 | :- make_system_term $value/1. 137 | 138 | 139 | % $map_keys defined in rexpr_builtins.clj 140 | % it returns a list of all of the keys contained in the map 141 | 142 | map_to_list_rec(Map, []) = []. 143 | map_to_list_rec(Map, [K|Rest]) = [V | map_to_list_rec(Map, Rest)] for Map = {K -> V | _}. 144 | $map_values(Map) = map_to_list_rec(Map, $map_keys(Map)). 145 | :- make_system_term $map_values/1. 146 | 147 | 148 | 149 | 150 | :- $clojure'{ 151 | (require 'clojure.data.csv) 152 | (require 'clojure.java.io) 153 | }. 154 | $read_csv(FileName) = $clojure'{ 155 | (with-open [reader (clojure.java.io/reader FileName)] 156 | (dyna.DynaTerm/make_list (map (fn [x] (dyna.DynaTerm/make_list x)) (clojure.data.csv/read-csv reader)))) 157 | }. 158 | :- make_system_term $read_csv/1. 159 | 160 | 161 | enusre_list_length([X|Rest], Length) := [X| ensure_list_length(Rest, Length-1)]. 162 | ensure_list_length([], L) := [$nil | ensure_list_length([], L-1)] for L > 0. 163 | ensure_list_length(_, 0) := []. 164 | 165 | define_term_from_csv(Name, Arity, []) = 166 | $constant[true]. % the base case 167 | define_term_from_csv(Name, Arity, [Row|Rest]) = 168 | Args = ensure_list_length(Row, Arity), 169 | $reflect(Head, Name, Args), 170 | ','[$define_term[Head, $nil, ":-", $constant[true]], 171 | define_term_from_csv(Name, Arity, Rest)]. 172 | 173 | 174 | % use this like :- import_csv(foo/3, "baz.csv"). 175 | :- macro $pragma_import_csv/1. 176 | $pragma_import_csv('/'[TermId, $constant[TermArity]], $constant[FileName]) = 177 | $reflect(TermId, TermName, 0), 178 | FileData = $read_csv(FileName), 179 | define_term_from_csv(TermName, TermArity, FileData). 180 | :- make_system_term $pragma_import_csv/2. 181 | 182 | % the compiler pragmas might look better if it was like `:- import_csv foo/3 183 | % "baz.csv".` But this is going to require that it somehow split on spaces 184 | % between different expressions. 185 | -------------------------------------------------------------------------------- /src/clojure/dyna/system.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.system 2 | (:require [dyna.utils :refer [def-tlocal tlocal tbinding debug-print]]) 3 | (:import [dyna DynaAgenda IDynaAgendaWork ThreadVar])) 4 | 5 | ;; variables which control how the system runs 6 | 7 | (def check-rexpr-arguments 8 | true 9 | ;false ; (Boolean/parseBoolean (System/getProperty "dyna.check_rexprs_args" "false")) 10 | ) 11 | 12 | (def print-rewrites-performed 13 | (Boolean/parseBoolean (System/getProperty "dyna.print_rewrites_performed" "false"))) 14 | 15 | (def track-where-rexpr-constructed 16 | false; (Boolean/parseBoolean (System/getProperty "dyna.trace_rexpr_construction" "false")) 17 | ) 18 | 19 | (def debug-statements 20 | (Boolean/parseBoolean (System/getProperty "dyna.debug" "true"))) 21 | 22 | (def status-counters 23 | dyna.StatusCounters/run_counters 24 | #_(= "true" (System/getProperty "dyna.status_counters" "true"))) 25 | 26 | #_(def default-recursion-limit 27 | ;; now in ThreadVar.java 28 | (Integer/valueOf (System/getProperty "dyna.recursion_limit" "20"))) 29 | 30 | 31 | #_(def ^:dynamic *auto-run-agenda-before-query* 32 | (Boolean/parseBoolean (System/getProperty "dyna.auto_run_agenda" "true"))) 33 | (def-tlocal auto-run-agenda-before-query) 34 | 35 | (def ^{:redef true} use-optimized-rexprs 36 | (Boolean/parseBoolean (System/getProperty "dyna.optimized_rexprs" "true"))) 37 | (def-tlocal use-optimized-disjunct) 38 | ;(def-tlocal use-optimized-rexprs) 39 | 40 | #_(def ^:dynamic *generate-new-jit-rewrites* false) 41 | (def-tlocal generate-new-jit-rewrites) 42 | (def-tlocal generate-new-jit-states) 43 | 44 | ;; terms which are included by the system. These will get automattically 45 | ;; replaced once the objects are created in the first place these should not be 46 | ;; recursive statements or anything 47 | (def system-defined-user-term (atom {})) 48 | 49 | ;; terms which are also like the system defined user terms, but these are 50 | ;; defined by the user using `:-make_system_term a/1.` These are assocated with 51 | ;; the current running dyna system, so it is marked as dynamic 52 | #_(def ^:dynamic globally-defined-user-term (atom {})) 53 | (def-tlocal globally-defined-user-term) 54 | 55 | ;; anytime that a user definition changes, there should be a corresponding 56 | ;; assumption which changes, there is going to need to be some atomic function 57 | ;; which is going to change 58 | ;(def ^:dynamic user-defined-assumptions (atom {})) 59 | 60 | ;; expressions which are defined by the user 61 | #_(def ^:dynamic user-defined-terms (atom {})) 62 | (def-tlocal user-defined-terms) 63 | 64 | ;; a map of which terms are exported from a file 65 | #_(def ^:dynamic user-exported-terms (atom {})) 66 | (def-tlocal user-exported-terms) 67 | 68 | ;; a set of java.net.URL objects of which files have been imported into the runnint system 69 | #_(def ^:dynamic imported-files (atom #{})) 70 | (def-tlocal imported-files) 71 | 72 | ;; the agenda is a priority queue of the work as well as a hash set which tracks what is already pushed. 73 | ;; Parallel processing could work by just processing work from the agenda in parallel. If there is multiple 74 | #_(def ^:dynamic work-agenda (DynaAgenda.)) 75 | (def-tlocal work-agenda) 76 | 77 | ;; how many times a user-defined function can be expanded before we stop expanding 78 | #_(def ^:dynamic user-recursion-limit (atom default-recursion-limit)) ;; this does not need to really be an atom? it can just hold the value directly 79 | (def-tlocal user-recursion-limit) 80 | 81 | ;; if a query is made, where it should get printed to 82 | #_(def ^:dynamic query-output println) 83 | (def-tlocal query-output) 84 | 85 | ;; in the parser expressions like `$0`, `$1`, .... can be replaced with an R-expr. This is done via this handler 86 | ;; this is used in the case of using the system like a database 87 | #_(def ^:dynamic parser-external-value (fn [index] 88 | (throw (RuntimeException. "External value handler not set")))) 89 | (def-tlocal parser-external-value) 90 | 91 | 92 | ;; metadata associated with all of the different types of dynabases 93 | #_(def ^:dynamic dynabase-metadata (atom {})) 94 | (def-tlocal dynabase-metadata) 95 | 96 | ;; this should ensure that there are system-defined-user-terms also. could have some flag which is "system is inited" and that it would parse 97 | ;; the prelude in the case that it wasn't already inited or something? It would want for 98 | (defn make-new-dyna-system [] 99 | {:globally-defined-user-term (atom {}) 100 | :user-defined-terms (atom {}) 101 | :user-exported-terms (atom {}) 102 | :imported-files (atom #{}) 103 | :work-agenda (DynaAgenda.) 104 | :user-recursion-limit (atom ThreadVar/default_recursion_limit) 105 | :query-output println 106 | :system-is-inited (atom false)}) 107 | 108 | #_(defn copy-dyna-system [system] 109 | ;; this should check that the agenda is empty? Or will have that we need to copy the agenda 110 | (merge (into {} (for [[k v] system] 111 | (if (instance? clojure.lang.Atom v) 112 | [k (atom @v)] 113 | [k v]))) 114 | {:work-agenda (DynaAgenda. ^DynaAgenda (:work-agenda system))})) 115 | 116 | #_(def ^:dynamic *dyna-active-system* nil) 117 | (def-tlocal dyna-active-system) 118 | 119 | (defmacro run-under-system [system & args] 120 | `(let [state# ~system] 121 | (tbinding [dyna-active-system state# 122 | user-defined-terms (:user-defined-terms state#) 123 | user-exported-terms (:user-exported-terms state#) 124 | imported-files (:imported-files state#) 125 | work-agenda (:work-agenda state#) 126 | user-recursion-limit (:user-recursion-limit state#) 127 | query-output (:query-output state#) 128 | globally-defined-user-term (:globally-defined-user-term state#)] 129 | (when-not @(:system-is-inited state#) 130 | ((find-var 'dyna.core/init-system)) 131 | (reset! (:system-is-inited state#) true)) 132 | ~@args))) 133 | 134 | (defn push-agenda-work [work] 135 | (if (instance? IDynaAgendaWork work) 136 | #_(if (= (.priority ^IDynaAgendaWork work) ##Inf) 137 | (.run ^IDynaAgendaWork work)) 138 | (.push_work ^DynaAgenda (tlocal work-agenda) work) ;; if something is inf, it should just get popped first, there is no need to run it during the pushing stage, which might end up in weird orderings of the tasks 139 | (.push_work ^DynaAgenda (tlocal work-agenda) (reify IDynaAgendaWork 140 | (run [this] (work)) 141 | ;; there should be some fixed priority configured for which 142 | ;; internal tasks run at. Then some stuff could run before it 143 | ;; when it wants, but the internal stuff should probably be 144 | ;; let to run first? 145 | (priority [this] 1e16))))) 146 | 147 | (defn run-agenda [] 148 | (.process_agenda ^DynaAgenda (tlocal work-agenda))) 149 | 150 | (defn maybe-run-agenda [] 151 | (when (tlocal *auto-run-agenda-before-query*) 152 | (run-agenda))) 153 | 154 | (defmacro converge-agenda [& body] 155 | ;; rerun the query in the case that there is work on the agenda 156 | ;; this can invoke the expression multiple times until it is fully done 157 | `(let [f# (fn [] ~@body)] 158 | (loop [] 159 | (run-agenda) 160 | (let [r# (f#)] 161 | (if (.is_done ^DynaAgenda (tlocal work-agenda)) 162 | r# 163 | (do 164 | (debug-print "DEBUGGING Rerunning because agenda work") 165 | (recur))))))) 166 | 167 | (defn is-agenda-converged? [] 168 | (.is_done ^DynaAgenda (tlocal work-agenda))) 169 | 170 | (defn should-stop-processing? [] 171 | (when (Thread/interrupted) 172 | (throw (InterruptedException.)))) 173 | -------------------------------------------------------------------------------- /dyna_python_module/dyna/java_wrapper.py: -------------------------------------------------------------------------------- 1 | import jpype as _jpype 2 | import os as _os 3 | import io as _io 4 | 5 | # make sure that the JVM is started and configured 6 | from . import java_configure_jvm 7 | 8 | __all__ = [] 9 | 10 | _jpype.JClass('java.lang.Object').__repr__ = lambda self: f'Backend<{str(self)}>' 11 | 12 | _interface = _jpype.JClass('dyna.DynaInterface').getInstance() 13 | 14 | _term_class = _jpype.JClass('dyna.DynaTerm') 15 | _map_class = _jpype.JClass('dyna.DynaMap') 16 | 17 | 18 | def _construct_make_method(name): 19 | def f(*args): 20 | return _interface.make_rexpr(name, args) 21 | f.__name__ = f'make_{name.replace("-", "_")}' 22 | return f 23 | 24 | make_variable = _interface.make_variable 25 | make_constant = _interface.make_constant 26 | 27 | __all__ += ['make_variable', 'make_constant'] 28 | 29 | # probably not going to construct an R-expr from python, so there is probably no reason to do this... 30 | for _name in {'unify', 'conjunct', 'disjunct', 'multiplicity', 'proj', 31 | 'aggregator', 'if', 'add', 'times', 'min', 'max', 'pow', 'exp', 32 | 'log', 'lessthan', 'lessthan-eq', 'greaterthan', 'greaterthan-eq', 33 | 'equals', 'not-equals', 'land', 'lor', 'not', 'sin', 'cos', 'tan', 34 | 'sinh', 'cosh', 'tanh', 'range', 'random'}: 35 | globals()[f'make_{_name.replace("-", "_")}'] = _construct_make_method(_name) 36 | __all__.append(f'make_{_name.replace("-", "_")}') 37 | 38 | # @_jpype.JImplementationFor('dyna.DynaTerm') 39 | # class DynaTermJavaClass: 40 | # def __len__(self): 41 | # return _interface.get_term_arity(self) 42 | # @property 43 | # def name(self): 44 | # return _interface.get_term_name(self) 45 | # # def __getitem__(self, i: int): 46 | # # assert isinstance(i, int) 47 | # # return cast_from_dyna(_interface.get_term_argument(self, i)) 48 | # def __repr__(self): 49 | # return str(self) 50 | 51 | class DynaTerm: 52 | __slots__ = ('_system', '_term') 53 | def __init__(self, name, *args): 54 | if name is not None: 55 | # make term with no reference instance 56 | self._system = DynaInstance 57 | args = _jpype.JObject[:](args) 58 | self._term = _term_class(name, args) 59 | 60 | @property 61 | def name(self): 62 | return str(_interface.get_term_name(self._term)) 63 | 64 | def __str__(self): 65 | return str(self._term) 66 | def __repr__(self): 67 | return str(self) 68 | 69 | def __len__(self): 70 | return _interface.get_term_arity(self._term) 71 | 72 | def __getitem__(self, i: int): 73 | val = _interface.get_term_argument(self._term, i) 74 | return DynaInstance.cast_from_dyna(self._system, val) 75 | 76 | 77 | def _cast_term_from_dyna(system, term): 78 | t = DynaTerm(None) 79 | t._system = system 80 | t._term = term 81 | return t 82 | 83 | __all__.append('DynaTerm') 84 | 85 | class Dynabase: 86 | __slots__ = ('_system', '_dynabase') 87 | def __init__(self, system, dynabase): 88 | self._system = system 89 | self._dynabase = dynabase 90 | 91 | def __getattr__(self, name): 92 | def func(*args): 93 | query_str = '$0.'+name+'(' + ','.join([f'${i+1}' for i in range(len(args))]) +') ?' 94 | q = self._system.run_query( 95 | query_str, self._dynabase, *args) 96 | if q: 97 | return q[0] 98 | func.__name__ = name 99 | return func 100 | 101 | def __str__(self): 102 | return f'Dynabase@{hash(self._dynabase)}' # the internal representation of these is gross 103 | def __repr__(self): 104 | return str(self) 105 | 106 | __all__.append('Dynabase') 107 | 108 | def _cast_map_from_dyna(system, value): 109 | m = dict(value.map_elements) 110 | return dict((DynaInstance.cast_from_dyna(system, k), DynaInstance.cast_from_dyna(system, v)) 111 | for k,v in m.items()) 112 | 113 | def _cast_dict_to_dyna(system, value): 114 | arr = [] 115 | for k,v in value.items(): 116 | arr.append(DynaInstance.cast_to_dyna(system, k)) 117 | arr.append(DynaInstance.cast_to_dyna(system, v)) 118 | return _map_class.create(arr) 119 | 120 | 121 | 122 | @_jpype.JImplements('dyna.OpaqueValue') 123 | class _OpaqueValue: 124 | def __init__(self, wrapped): 125 | self._wrapped = wrapped 126 | def __eq__(self, other): 127 | if isinstance(other, _OpaqueValue): 128 | return self._wrapped == other._wrapped 129 | return False 130 | def __hash__(self): 131 | return hash(self._wrapped) 132 | 133 | @_jpype.JImplements('dyna.ExternalFunction') 134 | class _ExternalFunctionWrapper: 135 | def __init__(self, system, wrapped): 136 | self.__system = system 137 | self.__wrapped = wrapped 138 | @_jpype.JOverride 139 | def call(self, args): 140 | return self.__system.cast_to_dyna(self.__wrapped(*[self.__system.cast_from_dyna(x) for x in args])) 141 | 142 | 143 | def _wrap(f): 144 | return lambda _,b: f(b) 145 | _cast_map = { 146 | _jpype.JClass('java.lang.Integer'): _wrap(int), 147 | _jpype.JClass('java.lang.Long'): _wrap(int), 148 | _jpype.JClass('java.lang.Short'): _wrap(int), 149 | _jpype.JClass('java.lang.Byte'): _wrap(int), 150 | _jpype.JClass('java.lang.Float'): _wrap(float), 151 | _jpype.JClass('java.lang.Double'): _wrap(float), 152 | _jpype.JClass('java.lang.Boolean'): _wrap(bool), 153 | _jpype.JClass('java.lang.Character'): _wrap(str), 154 | _jpype.JClass('java.lang.String'): _wrap(str), 155 | 156 | _jpype.JClass('dyna.Dynabase'): Dynabase, 157 | _jpype.JClass('dyna.DynaTerm'): _cast_term_from_dyna, 158 | _jpype.JClass('dyna.DynaMap'): _cast_map_from_dyna, 159 | } 160 | 161 | 162 | class DynaInstance: 163 | def __init__(self): 164 | self.__system = _interface.create_dyna_system() 165 | 166 | def run_string(self, x, *query_args): 167 | if not query_args: 168 | _interface.run_string(self.__system, x) 169 | else: 170 | args = _jpype.JObject[:]([self.cast_to_dyna(v) for v in query_args]) 171 | _interface.run_string(self.__system, x, args) 172 | 173 | def run_file(self, f): 174 | if isinstance(f, str): 175 | a = _os.path.abspath(f) 176 | assert _os.path.exists(a), "File not found" 177 | _interface.run_file(self.__system, 'file://' + a) 178 | elif isinstance(f, (_io.TextIOBase, _io.BufferedIOBase, _io.RawIOBase, _io.IOBase)): 179 | a = _os.path.abspath(f.name) 180 | _interface.run_file(self.__system, 'file://' + a) 181 | else: 182 | raise TypeError(f"Dyna does not know how to run type {type(f)}") 183 | 184 | def run_query(self, x, *query_args): 185 | if not query_args: 186 | res = _interface.run_query(self.__system, x) 187 | return [self.cast_from_dyna(v) for v in res] 188 | else: 189 | args = _jpype.JObject[:]([self.cast_to_dyna(v) for v in query_args]) 190 | res = _interface.run_query(self.__system, x, args) 191 | return [self.cast_from_dyna(v) for v in res] 192 | 193 | def define_function(self, name, arity, function): 194 | func = _ExternalFunctionWrapper(self, function) 195 | _interface.define_external_function(self.__system, name, arity, func) 196 | 197 | def cast_to_dyna(self, x): 198 | if isinstance(x, (str, int, float, bool, _term_class, _jpype.JObject)): 199 | return x 200 | elif isinstance(x, (list, tuple)): 201 | a = _jpype.JObject[:]([DynaInstance.cast_to_dyna(self, v) for v in x]) 202 | v = _term_class.make_list(a) 203 | return v 204 | elif isinstance(x, Dynabase): 205 | assert x._system is self 206 | return x._dynabase 207 | elif isinstance(x, DynaTerm): 208 | assert x._system is self or x._system is DynaInstance 209 | return x._term 210 | elif isinstance(x, dict): 211 | return _cast_dict_to_dyna(self, x) 212 | else: 213 | return _OpaqueValue(x) 214 | 215 | def cast_from_dyna(self, x): 216 | if isinstance(x, _term_class): 217 | l = x.list_to_array() 218 | if l is not None: 219 | return [DynaInstance.cast_from_dyna(self, v) for v in l] 220 | return x 221 | elif isinstance(x, _OpaqueValue): 222 | return x._wrapped 223 | t = type(x) 224 | if t in _cast_map: 225 | return _cast_map[t](self, x) 226 | return x 227 | 228 | 229 | __all__.append('DynaInstance') 230 | -------------------------------------------------------------------------------- /src/clojure/dyna/optimize_rexpr.clj: -------------------------------------------------------------------------------- 1 | (ns dyna.optimize-rexpr 2 | (:require [dyna.utils :refer :all]) 3 | (:require [dyna.rexpr]) 4 | (:require [dyna.base-protocols :refer :all]) 5 | (:require [dyna.rexpr-constructors :refer :all]) 6 | (:require [dyna.user-defined-terms :refer [user-rexpr-combined-no-memo]]) 7 | (:require [clojure.set :refer [union intersection]]) 8 | (:import [java.util IdentityHashMap])) 9 | 10 | 11 | ;; operations which work best when there is a bit of a higher level 12 | ;; "perspective" on the R-expr compared to only using the rewrites directly 13 | 14 | (defn optimize-aliased-variables 15 | ;; remove excess unify and project between variables which are unnecessary in the expression 16 | ([rexpr] (let [[unified-vars new-rexpr] (optimize-aliased-variables rexpr #{})] 17 | #_(when-not (= (exposed-variables rexpr) (exposed-variables new-rexpr)) 18 | (debug-repl "not equal expose")) 19 | new-rexpr)) 20 | ([rexpr proj-out-vars] 21 | (let [var-unifies (transient {}) 22 | mr (cond 23 | (is-proj? rexpr) 24 | (let [pvar (:var rexpr) 25 | prexpr (:body rexpr) 26 | [nested-unifies nested-rexpr] (optimize-aliased-variables prexpr (conj proj-out-vars proj-out-vars)) ;; why are we passing the projected out var here, seems unnecessary? 27 | self-var-unifies (disj (get nested-unifies pvar) pvar) ;; if there is any other variable here 28 | ret-rexpr (if-not (empty? self-var-unifies) 29 | (let [new-variable (if (some is-constant? self-var-unifies) 30 | (first (filter is-constant? self-var-unifies)) 31 | (first self-var-unifies))] 32 | (remap-variables nested-rexpr {pvar new-variable})) 33 | (if (= nested-rexpr prexpr) 34 | rexpr ;; no change, so just return ourself 35 | (make-proj pvar nested-rexpr)))] 36 | (doseq [[k v] nested-unifies 37 | :when (not= k pvar)] 38 | (assoc! var-unifies k (union (get var-unifies k) (disj v pvar)))) 39 | ;(dyna-assert (= (exposed-variables rexpr) (exposed-variables ret-rexpr))) 40 | ret-rexpr) 41 | 42 | (is-unify? rexpr) (let [[a b] (get-arguments rexpr)] 43 | (assoc! var-unifies a (conj (get var-unifies a #{}) b)) 44 | (assoc! var-unifies b (conj (get var-unifies b #{}) a)) 45 | rexpr) ;; there is no change to the expression here 46 | (is-disjunct? rexpr) rexpr ;; we do not evaluate disjunctions for variables which might get unified together 47 | :else ;; otherwise we should check all of the children of the expression to see if there is some structure 48 | (let [rr (rewrite-rexpr-children-no-simp rexpr 49 | (fn [r] 50 | (let [[unifies nr] (optimize-aliased-variables r proj-out-vars)] 51 | (doseq [[k v] unifies] 52 | (assoc! var-unifies k (union v (get var-unifies k)))) 53 | nr)))] 54 | rr))] 55 | [(persistent! var-unifies) mr]))) 56 | 57 | (defn optimize-lift-up-det-variables [rexpr] 58 | ;; variables which are determinstic from other variables which are set, should be lifted as high as possible in the context 59 | ;; this will allow for variables to get shared in multiple places without having to be aliased together 60 | ;; e.g. (proj X (sum-agg (proj Y (X=foo[Y])))) ===> (proj X (proj Y (X=foo[Y]) * (sum-agg ...))) 61 | (???) 62 | ) 63 | 64 | (defrecord redudant-constraint-var [id]) 65 | 66 | (defn- redudant-constraints [parent-replace-map rexpr] 67 | (let [conj-rexpr (all-conjunctive-rexprs rexpr) 68 | det-var-map (group-by :rexpr-remapped 69 | (for [[projected-vars cr] conj-rexpr 70 | :when (and (not (is-conjunct? cr)) (is-constraint? cr)) 71 | :let [vd (variable-functional-dependencies cr)] 72 | [ground-vars free-vars] vd] 73 | (let [i (volatile! 0) 74 | m (volatile! {}) 75 | remapped (remap-variables-func cr (fn [v] 76 | (if (contains? free-vars v) 77 | (let [g (make-variable (redudant-constraint-var. (vswap! i inc)))] 78 | (vswap! m assoc g v) 79 | g) 80 | v)))] 81 | {:projected-vars projected-vars 82 | :rexpr cr 83 | :require-ground ground-vars 84 | :free-vars free-vars 85 | :rexpr-remapped remapped 86 | :remap @m}))) 87 | replace-map (merge (into {} (for [[group-key all-constraints] det-var-map 88 | :let [possible-picks (remove (fn [x] (not (empty? (intersection (:projected-vars x) (:free-vars x))))) 89 | all-constraints)] 90 | :when (not (empty? possible-picks))] 91 | [group-key (first possible-picks)])) 92 | parent-replace-map ;; let the parent replace map work as an override so that if something already exists, we can still call it 93 | ) 94 | ^IdentityHashMap replace-rexprs (IdentityHashMap.)] 95 | (doseq [[group-key all-constraints] det-var-map 96 | :let [pick (get replace-map group-key)] 97 | :when (not (nil? pick)) 98 | to-replace (remove (partial identical? pick) all-constraints)] 99 | (assert (= (set (keys (:remap pick))) (set (keys (:remap to-replace))))) 100 | (.put replace-rexprs 101 | (:rexpr to-replace) 102 | (make-conjunct (vec (for [k (keys (:remap pick))] 103 | (make-no-simp-unify (strict-get (:remap pick) k) 104 | (strict-get (:remap to-replace) k))))))) 105 | (letfn [(remap-fn [r] 106 | (let [z (.get replace-rexprs r)] 107 | (cond (not (nil? z)) z 108 | 109 | (is-disjunct? r) (rewrite-rexpr-children r (partial redudant-constraints replace-map)) 110 | 111 | (is-disjunct-op? r) r 112 | 113 | :else (rewrite-rexpr-children r remap-fn))))] 114 | (remap-fn rexpr)))) 115 | 116 | (defn optimize-redudant-constraints [rexpr] 117 | #_(redudant-constraints {} rexpr)) 118 | 119 | 120 | (defn optimize-conjunct-of-disjuncts [rexpr] 121 | ;; there might be something which can only be optimized in the presence of 122 | ;; some subset of a disjunctive branch if we can eleminate those other 123 | ;; branches, then we might be able to identify that there is some branch which 124 | ;; we can entirely elminate from the program 125 | ) 126 | 127 | (defn optimize-refold-program [rexpr] 128 | ;; look for places where we might want to generate a new R-expr after we have expanded the program some depth 129 | ;; this might be useful in the case of recursive programs. 130 | ) 131 | 132 | #_(defn optimize-remove-unnecessary-nested-aggregators [rexpr] 133 | ;; the aggregator will get removed if there is only a single child in the 134 | ;; first place though if we are going to take the min of a min for example, 135 | ;; then we do not need to run two different aggregators. 136 | ) 137 | 138 | ;; (defn- check-rexpr-basecases 139 | ;; ([rexpr stack] 140 | ;; (if ) 141 | ;; ) 142 | ;; ([rexpr] (recur rexpr ()))) 143 | 144 | ;; (defn- get-all-conjunctive-rexprs [rexpr] 145 | ;; ()) 146 | 147 | 148 | #_(defn optimize-disjuncts [rexpr] 149 | ;; look through the disjuncts and figure out if there are any branches which 150 | ;; are unnecessary of where extra constraints could be added to the branches. 151 | 152 | ;; if 153 | ) 154 | --------------------------------------------------------------------------------