MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
eval.cpp
Go to the documentation of this file.
2
3#include <mim/lam.h>
4
5#include <mim/plug/cps/cps.h>
6
8
9using namespace std::literals;
10
12
13/// The high level view is:
14/// ```
15/// f: B -> C
16/// g: A -> B
17/// f o g := λ x. f(g(x)) : A -> C
18/// ```
19/// In CPS the types look like:
20/// ```
21/// f: Cn[B, Cn C]
22/// g: Cn[A, Cn B]
23/// h = f o g
24/// h: Cn[A, cn C]
25/// h = λ (a ret_h) = g (a, h')
26/// h': Cn B
27/// h'= λ b = f (b, ret_h)
28/// ```
29static const Def* compose_cn(const Def* f, const Def* g) {
30 auto& world = f->world();
31 auto F = f->type()->as<Pi>();
32 auto G = g->type()->as<Pi>();
33
34 assert(Pi::isa_returning(F));
35 assert(Pi::isa_returning(G));
36
37 auto A = G->dom(2, 0);
38 auto B = G->ret_dom();
39 auto C = F->ret_dom();
40 // The type check of codom G = dom F is better handled by the application type checking
41
42 world.log().d("compose f: {}: {}, g: {}: {}", f, F, g, G);
43 world.log().d(" A = {}, B = {}, C = {}", A, B, C);
44
45 auto name = "comp_"s + f->sym().str() + "_" + g->sym().str();
46 auto h = world.mut_fun(A, C)->set(name);
47 auto hcont = world.mut_con(B)->set(name + "_cont");
48
49 h->app(true, g, {h->var(2, 0), hcont});
50
51 auto hcont_var = hcont->var(); // Warning: not var(0) => only one var => normalization flattens tuples down here.
52 hcont->app(true, f, {hcont_var, h->var(2, 1) /* ret_var */});
53
54 return h;
55}
56
57// TODO: maybe use template (https://codereview.stackexchange.com/questions/141961/memoization-via-template) to memoize
58const Def* Eval::augment(const Def* def, Lam* f, Lam* f_diff) {
59 if (auto res = fe::lookup(augmented, def)) return res;
60 return augmented[def] = augment_(def, f, f_diff);
61}
62
63const Def* Eval::derive(const Def* def) {
64 if (auto res = fe::lookup(derived, def)) return res;
65 return derived[def] = derive_(def);
66}
67
68const Def* Eval::rewrite_imm_App(const App* app) {
69 if (auto ad_app = Axm::isa<ad>(app); ad_app) {
70 // callee = autodiff T
71 // arg = function of type T
72 // (or operator)
73 // Rewrite the argument into the new world first; the whole derivation then works on that copy.
74 auto arg = rewrite(ad_app->arg());
75
76 if (arg->isa<Lam>()) return derive(arg);
77
78 // TODO: handle operators analogous
79
80 assert(0 && "not implemented");
81 return arg;
82 }
83
84 return RWPhase::rewrite_imm_App(app);
85}
86
87/*
88 * toplevel derivation
89 */
90
91/// Additionally to the derivation, the pullback is registered and the maps are initialized.
92const Def* Eval::derive_(const Def* def) {
93 auto lam = def->as_mut<Lam>(); // TODO check if mutable
94 auto deriv_ty = autodiff_type_fun_pi(lam->type());
95 auto deriv = world().mut_lam(deriv_ty)->set(lam->sym().str() + "_deriv");
96
97 // We first pre-register the derivatives.
98 // This knowledge is needed for recursion.
99 // (Alternatively, we could also use projections out the variables instead of pre-partial-pullback
100 // initialization.)
101 derived[lam] = deriv;
102
103 auto [arg_ty, ret_pi] = lam->type()->doms<2>();
104 auto deriv_all_args = deriv->var();
105 const Def* deriv_arg = deriv->var(2, 0)->set("arg");
106
107 // We generate the shadow pullbacks dynamically to save work and avoid code duplication.
108 // Only the toplevel pullback for arguments and return continuation is special cased.
109
110 // TODO: check identity: could use identity tangent(arg_ty) = tangent(augment(arg_ty)) with deriv_arg->type() =
111 // augment(arg_ty) We give the argument the identity pullback.
112 auto arg_id_pb = id_pullback(arg_ty);
113 partial_pullback[deriv_arg] = arg_id_pb;
114 // The return continuation has to formally exist but should never be directly accessed.
115 auto ret_var = deriv->var(2, 1);
116 auto ret_pb = zero_pullback(lam->var(2, 1)->type(), arg_ty);
117 partial_pullback[ret_var] = ret_pb;
118
119 shadow_pullback[deriv_all_args] = world().tuple({arg_id_pb, ret_pb});
120
121 // We pre-register the augment replacements.
122 // The function and its variables are replaced by their new derived versions.
123 // TODO: maybe leave out function call (duplication with derived)
124 augmented[def] = deriv;
125 augmented[lam->var()] = deriv->var();
126
127 // already contains the correct application of
128 // deriv->ret_var() by specification
129 // f : cn[R] has a partial derivative (exception to closed rule)
130 // f': cn[R, cn[R, cn[A]]]
131 // this is needed for continuations (without closure conversion)
132 // but also essentially for the return continuation
133
134 // Here a reminder of types:
135 // The expression `e: B` has the implicit function `e_fun: A -> B`
136 // The partial pullback is then `e*: B* -> A*`
137 // The derivatived version is `e': B' × (B* -> A*)` which is an application of `e'_fun: A' -> B' × (B* -> A*)`
138 auto new_body = augment(lam->body(), lam, deriv);
139 deriv->set(true, new_body);
140
141 return deriv;
142}
143
144/*
145 * augment
146 */
147
148const Def* Eval::augment_lit(const Lit* lit, Lam* f, Lam*) {
149 auto pb = zero_pullback(lit->type(), f->dom(2, 0));
150 partial_pullback[lit] = pb;
151 return lit;
152}
153
154const Def* Eval::pullback(const Def* aug, Lam* f) {
155 // A singleton has no tangent, and its sole inhabitant is shared by all functions.
156 if (world().inhabitant(aug->type()) == aug) return zero_pullback(aug->type(), f->dom(2, 0));
157 return partial_pullback[aug];
158}
159
160const Def* Eval::augment_var(const Var* var, Lam*, Lam*) {
161 assert(augmented.count(var));
162 assert(partial_pullback.count(augmented[var]));
163 return var;
164}
165
166const Def* Eval::augment_lam(Lam* lam, Lam* f, Lam* f_diff) {
167 // TODO: we need partial pullbacks for tuples (higher-order / ret-cont application)
168 // also for higher-order args, ret_cont (at another point)
169 // the pullback is not important but formally required by tuple rule
170 if (augmented.count(lam)) {
171 // We already know the function:
172 // * recursion
173 // * higher order arguments
174 // * new encounter of previous function
175 return augmented[lam];
176 }
177 // TODO: better fix (another pass as analysis?)
178 // TODO: handle open functions
179 if (Lam::isa_basicblock(lam) || lam->sym().view().contains("ret") || lam->sym().view().contains("_cont")) {
180 // A open continuation behaves the same as return:
181 // ```
182 // cont: Cn[X]
183 // cont': Cn[X,Cn[X,A]]
184 // ```
185 // There is dependency on the closed function context.
186 // (All derivatives are with respect to the arguments of a closed function.)
187
188 auto cont_dom = lam->type()->dom(); // not only 0 but all
189 auto pb_ty = pullback_type(cont_dom, f->dom(2, 0));
190 auto aug_dom = autodiff_type_fun(cont_dom);
191 auto aug_lam = world().mut_con({aug_dom, pb_ty})->set("aug_"s + lam->sym().str());
192 auto aug_var = aug_lam->var(2, 0);
193 augmented[lam->var()] = aug_var;
194 augmented[lam] = aug_lam; // TODO: only one of these two
195 derived[lam] = aug_lam;
196 auto pb = aug_lam->var(2, 1);
197 partial_pullback[aug_var] = pb;
198 // We are still in same closed function.
199 auto new_body = augment(lam->body(), f, f_diff);
200 // TODO we also need to rewrite the filter
201 aug_lam->set(lam->filter(), new_body);
202
203 auto lam_pb = zero_pullback(lam->type(), f->dom(2, 0));
204 partial_pullback[aug_lam] = lam_pb;
205
206 return aug_lam;
207 }
208 // Some general function in the program needs to be differentiated.
209 // The old pass emitted a new `autodiff.ad` application here and relied on the PassMan to revisit it;
210 // as a Phase we derive eagerly instead (derive() pre-registers itself, so recursion terminates).
211 auto aug_lam = derive(lam);
212 // TODO: directly more association here? => partly inline op_autodiff
213 return aug_lam;
214}
215
216const Def* Eval::augment_extract(const Extract* ext, Lam* f, Lam* f_diff) {
217 auto tuple = ext->tuple();
218 auto index = ext->index();
219
220 auto aug_tuple = augment(tuple, f, f_diff);
221 auto aug_index = augment(index, f, f_diff);
222
223 const Def* pb;
224 if (shadow_pullback.count(aug_tuple)) {
225 auto shadow_tuple_pb = shadow_pullback[aug_tuple];
226 pb = world().extract(shadow_tuple_pb, aug_index);
227 } else {
228 // ```
229 // e:T, b:B
230 // b = e#i
231 // b* = \lambda (s:B). e* (insert s at i in (zero T))
232 // ```
233 auto tuple_pb = pullback(aug_tuple, f);
234 assert(tuple_pb);
235 auto pb_ty = pullback_type(ext->type(), f->dom(2, 0));
236 auto pb_fun = world().mut_lam(pb_ty)->set("extract_pb");
237 auto pb_tangent = pb_fun->var(2, 0)->set("s");
238 auto tuple_tan = world().insert(world().call<zero>(aug_tuple->type()), aug_index, pb_tangent)->set("tup_s");
239 pb_fun->app(true, tuple_pb, {tuple_tan, pb_fun->var(2, 1) /* ret_var but make sure to select correct one */});
240 pb = pb_fun;
241 }
242
243 auto aug_ext = world().extract(aug_tuple, aug_index);
244 partial_pullback[aug_ext] = pb;
245
246 return aug_ext;
247}
248
249const Def* Eval::augment_tuple(const Tuple* tup, Lam* f, Lam* f_diff) {
250 // TODO: should use ops instead?
251 auto aug_ops = tup->projs([&](const Def* op) -> const Def* { return augment(op, f, f_diff); });
252 auto aug_tup = world().tuple(aug_ops);
253
254 auto pbs = DefVec(Defs(aug_ops), [&](const Def* op) { return pullback(op, f); });
255 // shadow pb = tuple of pbs
256 auto shadow_pb = world().tuple(pbs);
257 shadow_pullback[aug_tup] = shadow_pb;
258
259 // ```
260 // \lambda (s:[E0,...,Em]).
261 // sum (m,A)
262 // ((cps2ds e0*) (s#0), ..., (cps2ds em*) (s#m))
263 // ```
264 auto pb_ty = pullback_type(tup->type(), f->dom(2, 0));
265 auto pb = world().mut_lam(pb_ty)->set("tup_pb");
266
267 auto pb_tangent = pb->var(2, 0)->set("tup_s");
268
269 auto tangents = DefVec(
270 pbs.size(), [&](nat_t i) { return world().app(cps::op_cps2ds_dep(pbs[i]), world().extract(pb_tangent, i)); });
271 pb->app(true, pb->var(2, 1),
272 // summed up tangents
273 op_sum(tangent_type_fun(f->dom(2, 0)), tangents));
274 partial_pullback[aug_tup] = pb;
275
276 return aug_tup;
277}
278
279const Def* Eval::augment_pack(const Pack* pack, Lam* f, Lam* f_diff) {
280 auto arity = pack->arity(); // TODO: arity vs shape
281 auto body = pack->body();
282
283 auto aug_arity = augment_(arity, f, f_diff);
284 auto aug_body = augment(body, f, f_diff);
285
286 auto aug_pack = world().pack(aug_arity, aug_body);
287
288 // TODO: or use scale axm
289 auto body_pb = pullback(aug_body, f);
290 assert(body_pb && "pack pullback should exists");
291 auto pb_pack = world().pack(aug_arity, body_pb);
292 shadow_pullback[aug_pack] = pb_pack;
293
294 auto pb_type = pullback_type(pack->type(), f->dom(2, 0));
295 auto pb = world().mut_lam(pb_type)->set("pack_pb");
296
297 auto f_arg_ty_diff = tangent_type_fun(f->dom(2, 0));
298 auto app_pb = world().mut_pack(world().arr(aug_arity, f_arg_ty_diff))->set_shape(aug_arity);
299
300 // TODO: special case for const width (special tuple)
301
302 // <i:n, cps2ds body_pb (s#i)>
303 app_pb->set_body(world().app(cps::op_cps2ds_dep(body_pb), world().extract(pb->var(2, 0), app_pb->var())));
304
305 auto sumup = world().app(world().annex<sum>(), {aug_arity, f_arg_ty_diff});
306
307 pb->app(true, pb->var(2, 1), world().app(sumup, app_pb));
308
309 partial_pullback[aug_pack] = pb;
310
311 return aug_pack;
312}
313
314const Def* Eval::augment_app(const App* app, Lam* f, Lam* f_diff) {
315 auto callee = app->callee();
316 auto arg = app->arg();
317
318 auto aug_arg = augment(arg, f, f_diff);
319 auto aug_callee = augment(callee, f, f_diff);
320
321 // TODO: move down to if(!is_cont(callee))
322 if (!Pi::isa_cn(callee->type()) && Pi::isa_cn(aug_callee->type())) aug_callee = cps::op_cps2ds_dep(aug_callee);
323
324 // nested (inner application)
325 if (app->type()->isa<Pi>()) {
326 auto aug_app = world().app(aug_callee, aug_arg);
327 // We do not add a pullback as the pullback is bundled in the cps call or returned by the ds call
328 return aug_app;
329 }
330
331 // continuation (ret, if, ...)
332 if (Pi::isa_basicblock(callee->type())) {
333 // TODO: check if function (not operator)
334 // The original function is an open function (return cont / continuation) of type `Cn[E]`
335 // The augmented function `aug_callee` looks like a function but is not really a function has the type `Cn[E,
336 // Cn[E, Cn[A]]]`
337
338 // ret(e) => ret'(e, e*)
339
340 auto arg_pb = pullback(aug_arg, f);
341 auto aug_app = world().app(aug_callee, {aug_arg, arg_pb});
342 return aug_app;
343 }
344
345 // ds function
346 if (!Pi::isa_cn(callee->type())) {
347 auto aug_app = world().app(aug_callee, aug_arg);
348
349 // The calle is ds function (e.g. operator (or its partial application))
350 auto [aug_res, fun_pb] = aug_app->projs<2>();
351 // We compose `fun_pb` with `argument_pb` to get the result pb
352 // TODO: combine case with cps function case
353 auto arg_pb = pullback(aug_arg, f);
354 assert(arg_pb);
355 // `fun_pb: out_tan -> arg_tan`
356 // `arg_pb: arg_tan -> fun_tan`
357 auto res_pb = compose_cn(arg_pb, fun_pb);
358 partial_pullback[aug_res] = res_pb;
359 return aug_res;
360 }
361
362 // TODO: dest with a function such that f args != g args
363 // normal function app
364 // ```
365 // g: cn[E, cn X]
366 // g(args,cont)
367 // g': cn[E, cn[X, cn[X, cn E]]]
368 // g'(aug_args, ____)
369 // ```
370 // At this point g_deriv might still be "autodiff ... g".
371 auto g_deriv = aug_callee;
372
373 auto [real_aug_args, aug_cont] = aug_arg->projs<2>();
374 auto e_pb = pullback(real_aug_args, f);
375
376 // TODO: better debug names
377 auto ret_g_deriv_ty = g_deriv->type()->as<Pi>()->dom(2, 1);
378 auto c1_ty = ret_g_deriv_ty->as<Pi>();
379 auto c1 = world().mut_lam(c1_ty)->set("c1");
380 auto res = c1->var(2, 0);
381 auto r_pb = c1->var(2, 1);
382 c1->app(true, aug_cont, {res, compose_cn(e_pb, r_pb)});
383
384 auto aug_app = world().app(aug_callee, {real_aug_args, c1});
385
386 // The result is * => no pb needed, no composition needed.
387 return aug_app;
388}
389
390/// Rewrites the given definition in a lambda environment.
391const Def* Eval::augment_(const Def* def, Lam* f, Lam* f_diff) {
392 // Applications are continuations, operators, or full functions.
393 if (auto app = def->isa<App>()) {
394 return augment_app(app, f, f_diff);
395 } else if (auto ext = def->isa<Extract>()) {
396 return augment_extract(ext, f, f_diff);
397 } else if (auto var = def->isa<Var>()) {
398 return augment_var(var, f, f_diff);
399 } else if (auto lam = def->isa_mut<Lam>()) {
400 return augment_lam(lam, f, f_diff);
401 } else if (auto lam = def->isa<Lam>()) {
402 log().e("cannot augment an immutable Lam: {}", lam);
403 assert(false && "can not handle non-mutable lambdas");
404 } else if (auto lit = def->isa<Lit>()) {
405 return augment_lit(lit, f, f_diff);
406 } else if (auto tup = def->isa<Tuple>()) {
407 return augment_tuple(tup, f, f_diff);
408 } else if (auto pack = def->isa<Pack>()) {
409 // TODO: handle mut packs (dependencies in the pack) (=> see paper about vectors)
410 return augment_pack(pack, f, f_diff);
411 } else if (auto ax = def->isa<Axm>()) {
412 auto diff_name = ax->sym().str();
413 fe::find_and_replace(diff_name, ".", "_");
414 diff_name = "autodiff.diff." + diff_name;
415
416 // Look the derivative up in the old world; rewrite() below maps it into the new one.
417 auto old_diff_fun = old_world().annex(old_world().sym(diff_name));
418 if (!old_diff_fun) {
419 auto expected_type = autodiff_type_fun(ax->type());
420 log().e("no derivative `{}`; expected type: {}", diff_name, expected_type);
421 assert(false && "unhandled axm");
422 }
423 // TODO: why does this cause a depth error?
424 return rewrite(old_diff_fun);
425 }
426
427 // TODO: handle Pi for axm app
428 // TODO: remaining (lambda, axm)
429
430 log().e("cannot augment {} node {}: {}", def->node_name(), def, def->type());
431 assert(false && "augment not implemented on this def");
432 std::unreachable();
433}
434
435} // namespace mim::plug::autodiff::phase
const Def * callee() const
Definition lam.h:237
const Def * arg() const
Definition lam.h:251
Definition axm.h:9
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:188
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:597
std::string_view node_name() const
Definition def.cpp:477
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:588
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:492
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
Sym sym() const
Definition def.h:633
const Def * arity() const
Number of elements available to Extract / Insert (may be dynamic).
Definition def.cpp:612
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
const Def * tuple() const
Definition tuple.h:370
const Def * index() const
Definition tuple.h:371
A function.
Definition lam.h:91
const Def * filter() const
Definition lam.h:103
const Pi * type() const
Definition lam.h:112
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:127
const Def * body() const
Definition lam.h:104
A (possibly paramterized) Tuple.
Definition tuple.h:332
flags_t annex() const
Definition phase.h:62
const fe::Log & log() const
Definition phase.h:60
A dependent function type.
Definition lam.h:14
const Def * ret_dom() const
Pi::domain of Pi::ret_pi.
Definition lam.h:58
static const Pi * isa_cn(const Def *d)
Definition lam.h:50
static const Pi * isa_basicblock(const Def *d)
Definition lam.h:52
const Def * dom() const
Definition lam.h:38
Pi * set(const Def *dom, const Def *codom)
Definition lam.h:65
static const Pi * isa_returning(const Def *d)
Definition lam.h:51
World & old_world()
Get old Defs from here.
Definition phase.h:378
const Def * rewrite_imm_App(const App *) override
Definition phase.cpp:223
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:62
const Def * body() const
Definition tuple.h:280
Data constructor for a Sigma.
Definition tuple.h:84
A variable introduced by a binder (mutable).
Definition def.h:857
const Def * annex(Sym sym)
Lookup annex by Sym.
Definition world.cpp:160
const Def * augment_app(const App *, Lam *, Lam *)
Definition eval.cpp:314
const Def * augment_extract(const Extract *, Lam *, Lam *)
Definition eval.cpp:216
const Def * augment_lit(const Lit *, Lam *, Lam *)
Definition eval.cpp:148
const Def * rewrite_imm_App(const App *) final
Detect autodiff calls.
Definition eval.cpp:68
const Def * derive(const Def *)
Acts on toplevel autodiff on closed terms:
Definition eval.cpp:63
const Def * augment_lam(Lam *, Lam *, Lam *)
Definition eval.cpp:166
const Def * augment_tuple(const Tuple *, Lam *, Lam *)
Definition eval.cpp:249
const Def * augment_var(const Var *, Lam *, Lam *)
helper functions for augment
Definition eval.cpp:160
const Def * augment(const Def *, Lam *, Lam *)
Applies to (open) expressions in a functional context.
Definition eval.cpp:58
const Def * augment_pack(const Pack *pack, Lam *f, Lam *f_diff)
Definition eval.cpp:279
const Def * pullback(const Def *aug, Lam *f)
The partial pullback of aug in the closed function f.
Definition eval.cpp:154
const Def * derive_(const Def *)
Additionally to the derivation, the pullback is registered and the maps are initialized.
Definition eval.cpp:92
const Def * augment_(const Def *, Lam *, Lam *)
Rewrites the given definition in a lambda environment.
Definition eval.cpp:391
static const Def * compose_cn(const Def *f, const Def *g)
The high level view is:
Definition eval.cpp:29
const Pi * autodiff_type_fun_pi(const Pi *)
Definition autodiff.cpp:85
const Def * op_sum(const Def *T, Defs)
Definition autodiff.cpp:154
const Def * autodiff_type_fun(const Def *)
Definition autodiff.cpp:107
const Def * tangent_type_fun(const Def *)
Definition autodiff.cpp:56
const Def * zero_pullback(const Def *E, const Def *A)
Definition autodiff.cpp:44
const Def * id_pullback(const Def *)
Definition autodiff.cpp:33
const Pi * pullback_type(const Def *E, const Def *A)
computes pb type E* -> A* E - type of the expression (return type for a function) A - type of the arg...
Definition autodiff.cpp:61
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
The tuple Plugin
u64 nat_t
Definition types.h:37
fe::View< const Def * > Defs
Definition def.h:96
fe::Vector< const Def * > DefVec
Definition def.h:98