graph.cc
Go to the documentation of this file.
1 /*
2  * MoMEMta: a modular implementation of the Matrix Element Method
3  * Copyright (C) 2017 Universite catholique de Louvain (UCL), Belgium
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
25 #include <catch.hpp>
26 
27 #include <momemta/ConfigurationReader.h>
28 #include <momemta/Logging.h>
29 
30 #include <Graph.h>
31 
32 Configuration get_conf(const std::string& conf) {
33  auto reader = ConfigurationReader("!" + conf);
34  return reader.freeze();
35 }
36 
37 TEST_CASE("Graph", "[core][graph]") {
38 
39  logging::set_level(logging::level::warning);
40 
41  momemta::ModuleList available_modules;
42  momemta::ModuleRegistry::get().exportList(false, available_modules);
43 
44  SECTION("Single module") {
45  const std::string conf_str = R"(
46 local input = declare_input("input")
47 
48 DoubleConstant.constant = { value = 42. }
49 
50 integrand("constant::value")
51 )";
52 
53  auto conf = get_conf(conf_str);
54 
55  REQUIRE(conf.getIntegrands().size() == 1);
56  REQUIRE(conf.getInputs().size() == 1);
57  REQUIRE(conf.getModules().size() == 5); // 1 user + 4 internals
58 
59  momemta::ComputationGraphBuilder builder(available_modules, conf);
60  auto graph = builder.build();
61 
62  REQUIRE(graph->getPaths().size() == 1);
63  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
64 
65  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
66  REQUIRE(modules.size() == 1);
67  REQUIRE(modules.front().type == "DoubleConstant");
68  REQUIRE(modules.front().name == "constant");
69  }
70 
71  SECTION("Unused module should be removed") {
72  const std::string conf_str = R"(
73 local input = declare_input("input")
74 
75 DoubleConstant.constant = { value = 42. }
76 
77 DoubleConstant.unused = { value = 42. }
78 
79 integrand("constant::value")
80 )";
81 
82  auto conf = get_conf(conf_str);
83 
84  REQUIRE(conf.getModules().size() == 6); // 2 user + 4 internals
85 
86  momemta::ComputationGraphBuilder builder(available_modules, conf);
87  auto graph = builder.build();
88 
89  REQUIRE(graph->getPaths().size() == 1);
90  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
91 
92  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
93  REQUIRE(modules.size() == 1);
94  }
95 
96  SECTION("Sticky module should not be removed") {
97  const std::string conf_str = R"(
98 local input = declare_input("input")
99 
100 DoubleConstant.constant = { value = 42. }
101 
102 DoublePrinter.unused = { input = "constant::value" }
103 
104 integrand("constant::value")
105 )";
106 
107  auto conf = get_conf(conf_str);
108 
109  REQUIRE(conf.getModules().size() == 6); // 2 user + 4 internals
110 
111  momemta::ComputationGraphBuilder builder(available_modules, conf);
112  auto graph = builder.build();
113 
114  REQUIRE(graph->getPaths().size() == 1);
115  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
116 
117  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
118  REQUIRE(modules.size() == 2);
119  }
120 
121  SECTION("Module should be correctly ordered") {
122  const std::string conf_str = R"(
123 local input_a = declare_input("input_a")
124 local input_b = declare_input("input_b")
125 
126 GaussianTransferFunctionOnEnergy.tf = {
127  ps_point = add_dimension(),
128  reco_particle = "input_sum::output",
129  sigma = 0.05
130 }
131 
132 VectorLinearCombinator.input_sum = {
133  inputs = { input_a.reco_p4, input_b.reco_p4 },
134  coefficients = {1, 1}
135 }
136 
137 integrand("tf::output")
138 )";
139 
140  auto conf = get_conf(conf_str);
141 
142  REQUIRE(conf.getModules().size() == 7); // 2 user + 5 internals
143 
144  momemta::ComputationGraphBuilder builder(available_modules, conf);
145  auto graph = builder.build();
146 
147  REQUIRE(graph->getPaths().size() == 1);
148  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
149 
150  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
151  REQUIRE(modules.size() == 2);
152 
153  REQUIRE(modules.at(0).name == "input_sum");
154  REQUIRE(modules.at(1).name == "tf");
155  }
156 
157  SECTION("Circular dependencies are not allowed") {
158  const std::string conf_str = R"(
159 GaussianTransferFunctionOnEnergy.tf_1 = {
160  ps_point = "dummy::dummy",
161  reco_particle = "tf_2::output",
162  sigma = 0.05
163 }
164 
165 GaussianTransferFunctionOnEnergy.tf_2 = {
166  ps_point = "dummy::dummy",
167  reco_particle = "tf_1::output",
168  sigma = 0.05
169 }
170 )";
171 
172  auto conf = get_conf(conf_str);
173 
174  REQUIRE(conf.getModules().size() == 5); // 2 user + 3 internals
175 
176  logging::set_level(logging::level::off);
177  momemta::ComputationGraphBuilder builder(available_modules, conf);
178  REQUIRE_THROWS(builder.build());
179  }
180 
181  SECTION("Two execution paths") {
182  const std::string conf_str = R"(
183 
184 DoubleConstant.dummy = { value = 42. }
185 
186 Looper.looper = {
187  solutions = "dummy::value",
188  path = Path("sum", "constant_1", "constant_2", "printer")
189 }
190 
191 SolutionPrinter.printer = { input = "looper::particles" }
192 DoubleConstant.constant_1 = { value = 42. }
193 DoubleConstant.constant_2 = { value = 42. }
194 
195 DoubleLinearCombinator.sum = {
196  inputs = { "constant_1::value", "constant_2::value" },
197  coefficients = {1, 1}
198 }
199 
200 integrand("sum::output")
201 )";
202 
203  auto conf = get_conf(conf_str);
204 
205  REQUIRE(conf.getModules().size() == 9); // 6 user + 3 internals
206  REQUIRE(conf.getPaths().size() == 1);
207 
208  momemta::ComputationGraphBuilder builder(available_modules, conf);
209  auto graph = builder.build();
210 
211  REQUIRE(graph->getPaths().size() == 2);
212  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
213  REQUIRE(graph->getPaths().back() == conf.getPaths().front()->id);
214 
215  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
216  REQUIRE(modules.size() == 2); // Only the looper + dummy module
217 
218  REQUIRE(modules.at(0).name == "dummy");
219  REQUIRE(modules.at(1).name == "looper");
220 
221  modules = graph->getDecls(graph->getPaths().back());
222  // Order is not guaranteed, only test the number of modules
223  REQUIRE(modules.size() == 4);
224  }
225 
226  SECTION("A module using looper's output must be inside the looper execution path") {
227  const std::string conf_str = R"(
228 DoubleConstant.dummy = { value = 42. }
229 
230 Looper.looper = {
231  solutions = "dummy::value",
232  path = Path("constant")
233 }
234 
235 SolutionPrinter.printer = { input = "looper::particles" }
236 DoubleConstant.constant = { value = 42. }
237 
238 integrand("sum::output")
239 )";
240 
241  auto conf = get_conf(conf_str);
242 
243  REQUIRE(conf.getModules().size() == 7); // 4 user + 3 internals
244  REQUIRE(conf.getPaths().size() == 1);
245 
246  logging::set_level(logging::level::off);
247  momemta::ComputationGraphBuilder builder(available_modules, conf);
248  REQUIRE_THROWS_WITH(
249  builder.build(),
250  Catch::Matchers::Contains("A module is using the looper output but not actually part of its execution path")
251  );
252  }
253 
254  SECTION("A module inside the execution path may not exists") {
255  const std::string conf_str = R"(
256 DoubleConstant.dummy = { value = 42. }
257 
258 Looper.looper = {
259  solutions = "dummy::value",
260  path = Path("unexisting", "printer")
261 }
262 
263 SolutionPrinter.printer = { input = "looper::particles" }
264 
265 integrand("dummy::dummy")
266 )";
267 
268  auto conf = get_conf(conf_str);
269 
270  REQUIRE(conf.getModules().size() == 6); // 3 user + 3 internals
271  REQUIRE(conf.getPaths().size() == 1);
272 
273  logging::set_level(logging::level::off);
274  momemta::ComputationGraphBuilder builder(available_modules, conf);
275  auto graph = builder.build();
276  }
277 
278  SECTION("Three execution paths") {
279  const std::string conf_str = R"(
280 
281 DoubleConstant.dummy_1 = { value = 42. }
282 DoubleConstant.dummy_2 = { value = 42. }
283 
284 Looper.looper_2 = {
285  solutions = "dummy_2::value",
286  path = Path("printer_2")
287 }
288 
289 Looper.looper_1 = {
290  solutions = "dummy_1::value",
291  path = Path("printer_1", "looper_2", "dummy_2")
292 }
293 
294 SolutionPrinter.printer_1 = { input = "looper_1::particles" }
295 SolutionPrinter.printer_2 = { input = "looper_2::particles" }
296 
297 integrand("some::output")
298 )";
299 
300  auto conf = get_conf(conf_str);
301 
302  REQUIRE(conf.getModules().size() == 9); // 6 user + 3 internals
303  REQUIRE(conf.getPaths().size() == 2);
304 
305  momemta::ComputationGraphBuilder builder(available_modules, conf);
306  auto graph = builder.build();
307 
308  REQUIRE(graph->getPaths().size() == 3);
309  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
310 
311  // Last execution path should the one from looper_2, declared first in the configuration
312  REQUIRE(graph->getPaths().back() == conf.getPaths().front()->id);
313 
314  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
315  REQUIRE(modules.size() == 2); // Only looper_1 + dummy module
316 
317  REQUIRE(modules.at(0).name == "dummy_1");
318  REQUIRE(modules.at(1).name == "looper_1");
319 
320  modules = graph->getDecls(graph->getPaths().at(1));
321 
322  // Looper_1 path, dummy_1, printer_1 and looper_2
323  REQUIRE(modules.size() == 3);
324 
325  modules = graph->getDecls(graph->getPaths().at(2));
326 
327  REQUIRE(modules.size() == 1);
328  REQUIRE(modules.at(0).name == "printer_2");
329  }
330 
331  SECTION("Unused module should not increase the number of dimension") {
332  const std::string conf_str = R"(
333 
334 local first_dim = add_dimension()
335 local unused_dim = add_dimension()
336 
337 DoubleConstant.dummy = { value = 42. }
338 
339 GaussianTransferFunctionOnEnergy.tf_1 = {
340  ps_point = first_dim,
341  reco_particle = "dummy::value",
342  sigma = 0.05
343 }
344 
345 GaussianTransferFunctionOnEnergy.tf_2 = {
346  ps_point = first_dim,
347  reco_particle = "dummy::value",
348  sigma = 0.05
349 }
350 
351 GaussianTransferFunctionOnEnergy.tf_3 = {
352  ps_point = add_dimension(),
353  reco_particle = "dummy::value",
354  sigma = 0.05
355 }
356 
357 GaussianTransferFunctionOnEnergy.unused = {
358  ps_point = unused_dim,
359  reco_particle = "dummy::value",
360  sigma = 0.05
361 }
362 
363 integrand("tf_1::output", "tf_2::output", "tf_3::output")
364 )";
365 
366  auto conf = get_conf(conf_str);
367 
368  // Just parsing the configuration file leads to 3 dimensions, with one requested by a unused module
369  REQUIRE(conf.getNDimensions() == 3);
370 
371  momemta::ComputationGraphBuilder builder(available_modules, conf);
372  auto graph = builder.build();
373 
374  // The dimension requested by the unused module should be discarded
375  REQUIRE(graph->getNDimensions() == 2);
376 
377  REQUIRE(graph->getPaths().size() == 1);
378  REQUIRE(graph->getPaths().front() == DEFAULT_EXECUTION_PATH);
379 
380  auto modules = graph->getDecls(DEFAULT_EXECUTION_PATH);
381  REQUIRE(modules.size() == 4);
382  }
383 
384  SECTION("Using a non-existing input should throw an exception") {
385  const std::string conf_str = R"(
386 
387 GaussianTransferFunctionOnEnergy.tf_1 = {
388  ps_point = add_dimension(),
389  reco_particle = "non_existing_module::output",
390  sigma = 0.05
391 }
392 
393 integrand("tf_1::output")
394 )";
395 
396  auto conf = get_conf(conf_str);
397  logging::set_level(logging::level::off);
398  momemta::ComputationGraphBuilder builder(available_modules, conf);
399  REQUIRE_THROWS_WITH(
400  builder.build(),
401  Catch::Matchers::Equals("Module 'tf_1' requested a non-existing input (non_existing_module::output)")
402  );
403  }
404 
405  SECTION("Using a non-existing input should throw an exception") {
406  const std::string conf_str = R"(
407 
408 DoubleConstant.dummy = { value = 42. }
409 
410 GaussianTransferFunctionOnEnergy.tf_1 = {
411  ps_point = add_dimension(),
412  reco_particle = "dummy::non_existing_param",
413  sigma = 0.05
414 }
415 
416 integrand("tf_1::output")
417 )";
418 
419  auto conf = get_conf(conf_str);
420  logging::set_level(logging::level::off);
421  momemta::ComputationGraphBuilder builder(available_modules, conf);
422  REQUIRE_THROWS_WITH(
423  builder.build(),
424  Catch::Matchers::Equals("Module 'tf_1' requested a non-existing input (dummy::non_existing_param)")
425  );
426  }
427 }
A lua configuration file parser.
static ModuleRegistry & get()
A singleton available at startup.
A frozen snapshot of the configuration file.
Definition: Configuration.h:36
void exportList(bool ignore_internal, ModuleList &list) const