Skip to content

Commit fa04c26

Browse files
committed
feat: Add CONDITIONED BY * EXCEPT capability
1 parent 47157f7 commit fa04c26

File tree

2 files changed

+95
-58
lines changed

2 files changed

+95
-58
lines changed

resources/inferenceql/query/base.bnf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ select-list ::= select-star-clause
8080
/ selection (ws? ',' ws? selection)*
8181
/ aggregation (ws? ',' ws? aggregation)*
8282

83-
select-star-clause ::= star (ws? select-except-clause)?
8483
star ::= '*'
84+
select-star-clause ::= star (ws? select-except-clause)?
8585
select-except-clause ::= #'(?i)EXCEPT' ws? '(' ws? identifier-list ws? ')'
8686

8787
selection ::= (scalar-expr | aggregation) (ws alias-clause)?
@@ -225,7 +225,10 @@ density-event-and ::= density-event-1 (ws #'(?i)AND' ws density-event-1)+
225225

226226
density-event-group ::= '(' ws? density-event ws? ')'
227227

228-
conditioned-by-expr ::= model-expr ws #'(?i)CONDITIONED' ws #'(?i)BY' ws ('*' | density-event)
228+
conditioned-by-expr ::= model-expr ws #'(?i)CONDITIONED' ws #'(?i)BY' ws (conditioned-by-star-clause | density-event)
229+
<conditioned-by-star-clause> ::= star (ws? conditioned-by-except-clause)?
230+
conditioned-by-except-clause ::= #'(?i)EXCEPT' (ws? <'('> ws? model-var-list ws? <')'> | ws model-var-list)
231+
229232

230233
incorporate-expr ::= #'(?i)INCORPORATE' ws relation-expr ws #'(?i)INTO' ws model-expr
231234

src/inferenceql/query/scalar.cljc

Lines changed: 90 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,76 +23,97 @@
2323
[node]
2424
(-> node tree/only-child-node (nth 1)))
2525

26+
(declare plan)
27+
28+
(defn ^:private conditioned-by-plan*
29+
"`plan` helper that generates plans for CONDITIONED BY nodes.
30+
31+
NB: Exists because https://clojure.atlassian.net/browse/CLJ-1852 prevents us
32+
from directly adding these rules into `plan`."
33+
[node]
34+
(match/match node
35+
[:conditioned-by-expr model _conditioned _by [:star _]]
36+
`(~'iql/condition-all ~(plan model))
37+
[:conditioned-by-expr model _conditioned _by [:star _] [:conditioned-by-except-clause & except-children]]
38+
`(~'iql/condition-all-except ~(plan model) ~(plan (into [:conditioned-by-except-clause] except-children)))
39+
[:conditioned-by-expr model _conditioned _by child]
40+
`(~'iql/condition ~(plan model) ~(plan child))
41+
[:conditioned-by-except-clause _except model-var-list]
42+
(plan model-var-list)))
2643

2744
(defn plan
2845
"Given a parse tree/node, returns an execution plan."
2946
[node]
30-
(match/match (into (empty node)
31-
(remove tree/whitespace?)
32-
node)
33-
[:scalar-expr child] (plan child)
34-
[:scalar-expr-group "(" child ")"] (plan child)
47+
(let [ws-free-node (into (empty node)
48+
(remove tree/whitespace?)
49+
node)]
50+
(match/match ws-free-node
51+
[:scalar-expr child] (plan child)
52+
[:scalar-expr-group "(" child ")"] (plan child)
53+
54+
[:expr-not _not child] `(~'not ~(plan child))
55+
56+
[:expr-disjunction left _ right] `(~'or ~(plan left) ~(plan right))
57+
[:expr-conjunction left _ right] `(~'and ~(plan left) ~(plan right))
58+
[:expr-addition left _ right] `(~'+ ~(plan left) ~(plan right))
59+
[:expr-addition left _ right] `(~'+ ~(plan left) ~(plan right))
60+
[:expr-subtraction left _ right] `(~'- ~(plan left) ~(plan right))
61+
[:expr-multiplication left _ right] `(~'* ~(plan left) ~(plan right))
62+
[:expr-division left _ right] `(~'/ ~(plan left) ~(plan right))
3563

36-
[:expr-not _not child] `(~'not ~(plan child))
64+
[:expr-function-call-log _log child _] `(~'log ~(plan child))
3765

38-
[:expr-disjunction left _ right] `(~'or ~(plan left) ~(plan right))
39-
[:expr-conjunction left _ right] `(~'and ~(plan left) ~(plan right))
40-
[:expr-addition left _ right] `(~'+ ~(plan left) ~(plan right))
41-
[:expr-addition left _ right] `(~'+ ~(plan left) ~(plan right))
42-
[:expr-subtraction left _ right] `(~'- ~(plan left) ~(plan right))
43-
[:expr-multiplication left _ right] `(~'* ~(plan left) ~(plan right))
44-
[:expr-division left _ right] `(~'/ ~(plan left) ~(plan right))
66+
[:expr-binop left [:binop [:is _]] right] `(~'= ~(plan left) ~(plan right))
67+
[:expr-binop left [:binop [:is-not & _]] right] `(~'not= ~(plan left) ~(plan right))
68+
;; MUST not str-ify below, binops aren't identifiers.
69+
[:expr-binop left [:binop s] right] `(~(symbol s) ~(plan left) ~(plan right))
4570

46-
[:expr-function-call-log _log child _] `(~'log ~(plan child))
71+
[:distribution-event child] (plan child)
4772

48-
[:expr-binop left [:binop [:is _]] right] `(~'= ~(plan left) ~(plan right))
49-
[:expr-binop left [:binop [:is-not & _]] right] `(~'not= ~(plan left) ~(plan right))
50-
;; MUST not str-ify below, binops aren't identifiers.
51-
[:expr-binop left [:binop s] right] `(~(symbol s) ~(plan left) ~(plan right))
73+
[:distribution-event-or left _or right] [:or (plan left) (plan right)]
74+
[:distribution-event-and left _and right] [:and (plan left) (plan right)]
5275

53-
[:distribution-event child] (plan child)
76+
[:distribution-event-binop (variable :guard (tree/tag-pred :variable)) [:binop s] (scalar :guard (tree/tag-pred :scalar-expr))] [(keyword s) (plan variable) (plan scalar)]
77+
[:distribution-event-binop (scalar :guard (tree/tag-pred :scalar-expr)) [:binop s] (variable :guard (tree/tag-pred :variable))] [(keyword s) (plan variable) (plan scalar)]
5478

55-
[:distribution-event-or left _or right] [:or (plan left) (plan right)]
56-
[:distribution-event-and left _and right] [:and (plan left) (plan right)]
79+
[:distribution-event-group "(" child ")"] (plan child)
5780

58-
[:distribution-event-binop (variable :guard (tree/tag-pred :variable)) [:binop s] (scalar :guard (tree/tag-pred :scalar-expr))] [(keyword s) (plan variable) (plan scalar)]
59-
[:distribution-event-binop (scalar :guard (tree/tag-pred :scalar-expr)) [:binop s] (variable :guard (tree/tag-pred :variable))] [(keyword s) (plan variable) (plan scalar)]
81+
[:density-event child] (plan child)
82+
[:density-event-and & children] (into {} (comp (filter tree/branch?) (map plan)) children)
6083

61-
[:distribution-event-group "(" child ")"] (plan child)
84+
[:density-event-eq (variable :guard (tree/tag-pred :variable)) _= (scalar :guard (tree/tag-pred :scalar-expr))] {(plan variable) (plan scalar)}
85+
[:density-event-eq (scalar :guard (tree/tag-pred :scalar-expr)) _= (variable :guard (tree/tag-pred :variable))] {(plan variable) (plan scalar)}
6286

63-
[:density-event child] (plan child)
64-
[:density-event-and & children] (into {} (comp (filter tree/branch?) (map plan)) children)
87+
[:density-event-group "(" child ")"] (plan child)
6588

66-
[:density-event-eq (variable :guard (tree/tag-pred :variable)) _= (scalar :guard (tree/tag-pred :scalar-expr))] {(plan variable) (plan scalar)}
67-
[:density-event-eq (scalar :guard (tree/tag-pred :scalar-expr)) _= (variable :guard (tree/tag-pred :variable))] {(plan variable) (plan scalar)}
89+
[:probability-expr _prob _of event _under model] `(~'iql/prob ~(plan model) ~(plan event))
90+
[:density-expr _prob _density _of event _under model] `(~'iql/pdf ~(plan model) ~(plan event))
6891

69-
[:density-event-group "(" child ")"] (plan child)
92+
[:mutual-info-expr _m _i _of lhs _with rhs _under model] `(~'iql/mutual-info ~(plan model) ~(vec (plan lhs)) ~(vec (plan rhs)))
93+
[:approx-mutual-info-expr _a _m _i _of lhs _with rhs _under model] `(~'iql/approx-mutual-info ~(plan model) ~(vec (plan lhs)) ~(vec (plan rhs)))
7094

71-
[:probability-expr _prob _of event _under model] `(~'iql/prob ~(plan model) ~(plan event))
72-
[:density-expr _prob _density _of event _under model] `(~'iql/pdf ~(plan model) ~(plan event))
95+
[:model-expr child] (plan child)
96+
[:model-expr "(" child ")"] (plan child)
7397

74-
[:mutual-info-expr _m _i _of lhs _with rhs _under model] `(~'iql/mutual-info ~(plan model) ~(vec (plan lhs)) ~(vec (plan rhs)))
75-
[:approx-mutual-info-expr _a _m _i _of lhs _with rhs _under model] `(~'iql/approx-mutual-info ~(plan model) ~(vec (plan lhs)) ~(vec (plan rhs)))
98+
#?@(:clj [[:generative-table-expr _generative _table relation]
99+
(let [query-plan (requiring-resolve 'inferenceql.query.plan/plan)]
100+
`(~'iql/eval-relation-plan (~'quote ~(query-plan relation))))])
76101

77-
[:model-expr child] (plan child)
78-
[:model-expr "(" child ")"] (plan child)
102+
;; Matches either :conditioned-by-expr or :conditioned-by-except-clause
103+
;; and defers to conditioned-by-plan* to avoid https://clojure.atlassian.net/browse/CLJ-1852
104+
[(:or :conditioned-by-expr :conditioned-by-except-clause) & _] (conditioned-by-plan* ws-free-node)
79105

80-
#?@(:clj [[:generative-table-expr _generative _table relation]
81-
(let [query-plan (requiring-resolve 'inferenceql.query.plan/plan)]
82-
`(~'iql/eval-relation-plan (~'quote ~(query-plan relation))))])
106+
[:constrained-by-expr model _constrained _by event] `(~'iql/constrain ~(plan model) ~(plan event))
83107

84-
[:conditioned-by-expr model _conditioned _by "*"] `(~'iql/condition-all ~(plan model))
85-
[:conditioned-by-expr model _conditioned _by event] `(~'iql/condition ~(plan model) ~(plan event))
86-
[:constrained-by-expr model _constrained _by event] `(~'iql/constrain ~(plan model) ~(plan event))
87108

88-
[:value child] (literal/read child)
109+
[:value child] (literal/read child)
89110

90-
[:variable _var child] (id-node->str child)
91-
[:variable-list & variables] (map plan variables)
111+
[:variable _var child] (id-node->str child)
112+
[:variable-list & variables] (into [] (comp (filter tree/branch?) (map plan)) variables) ; remove commas
92113

93-
[:identifier child] (plan child)
94-
[:delimited-symbol s] (list 'iql/safe-get 'iql-bindings s)
95-
[:simple-symbol s] (list 'iql/safe-get 'iql-bindings s)))
114+
[:identifier child] (plan child)
115+
[:delimited-symbol s] (list 'iql/safe-get 'iql-bindings s)
116+
[:simple-symbol s] (list 'iql/safe-get 'iql-bindings s))))
96117

97118
(defn inference-event
98119
[event]
@@ -138,14 +159,26 @@
138159
(gpm/condition conditions))))))
139160

140161
(defn condition-all
141-
[model bindings]
142-
(let [conditions (reduce (fn [conditions variable]
143-
(cond-> conditions
144-
(contains? bindings variable)
145-
(assoc variable (get bindings variable))))
146-
{}
147-
(map str (gpm/variables model)))]
148-
(condition model conditions)))
162+
"Retrieves all variables from the model and conditions them based on the
163+
value found in the bindings, which includes the current tuple/row.
164+
165+
The 3-arity version takes an additional coll of vars to exclude."
166+
([model bindings]
167+
(condition-all model #{} bindings))
168+
([model exclusions bindings]
169+
(let [exclusions (set exclusions)
170+
condition-vars (into []
171+
(comp
172+
(map name)
173+
(filter (complement exclusions)))
174+
(gpm/variables model))
175+
conditions (reduce (fn [conditions variable]
176+
(cond-> conditions
177+
(contains? bindings variable)
178+
(assoc variable (get bindings variable))))
179+
{}
180+
condition-vars)]
181+
(condition model conditions))))
149182

150183
(defn operation?
151184
"Given an event form, returns `true` if the form is an operation."
@@ -273,7 +306,8 @@
273306
#?@(:clj ['eval-relation-plan
274307
(let [eval (requiring-resolve 'inferenceql.query.plan/eval)]
275308
#(generative-table/generative-table (eval % env bindings)))])
276-
#?@(:clj ['condition-all #(condition-all % bindings)])
309+
#?@(:clj ['condition-all #(condition-all % bindings)
310+
'condition-all-except #(condition-all %1 %2 bindings)])
277311
'condition condition
278312
'constrain constrain
279313
'mutual-info mutual-info

0 commit comments

Comments
 (0)