1 #pragma once 2 3 #include <pybind11/pybind11.h> 4 5 #include "../Kernel.hh" 6 7 namespace cadabra { 8 9 /// \ingroup pythoncore 10 /// 11 /// Setup of kernels in current scope, callable from Python. 12 /// When the decision was made to graft Cadabra onto Python, a choice had 13 /// to be made about how Python variable scope would influence the 14 /// visibility of Cadabra properties. It clearly makes sense to be able to 15 /// declare properties which only hold inside a particular 16 /// function. However Cadabra expressions and properties do not directly 17 /// correspond to Python objects. Rather, declaring a property is more 18 /// like a function call into the Cadabra module, which leaves its imprint 19 /// on the state of the C++ part but does not change anything on the 20 /// Python side, as you typically do not assign the created property to a 21 /// Python symbol. Therefore, properties do not naturally inherit Python's 22 /// scoping rules (this is different from e.g.~SymPy, in which 23 /// mathematical objects are always in one-to-one correspondence with a 24 /// Python object). A more fundamental problem is that properties can be 25 /// attached to patterns, and those patterns can involve more than just 26 /// the symbols which one passes into a function. 27 /// 28 /// In order to not burden the user, properties are therefore by default 29 /// global variables, stored in a single global Cadabra object 30 /// `__cdbkernel__` which is initialised at import of the Cadabra module. 31 /// If you add new properties inside a function scope, these will go 32 /// into this already existing *global* property list by default. 33 /// If you want to create a local scope for your computations, create a 34 /// new `__cdbkernel__` as in 35 /// ``` 36 /// def fun(): 37 /// __cdbkernel__ = cadabra.create_scope(); 38 /// [your code here] 39 /// ``` 40 /// Now computations will not see the global properties at all. 41 /// If you want to import the global properties, use instead 42 /// ``` 43 /// def fun(): 44 /// __cdbkernel__ = cadabra.create_scope_from_global() 45 /// [your code here] 46 /// ``` 47 /// It is crucial that the 48 /// `__cdbkernel__` symbol is referenced from within Python and visible to the bytecompiler, because 49 /// it is not possible to create new variables on the local stack at runtime. 50 /// Internally, the second version above fetches, at runtime, the 51 /// `__cdbkernel__` from the globals stack, copies all properties in there 52 /// into a new kernel, and returns the latter. 53 /// 54 /// Both versions above do populate the newly created kernel with 55 /// Cadabra's default properties. If you want a completely clean slate 56 /// (for e.g.~testing purposes, or because you really do not want default 57 /// rules for sums and products), use 58 /// ``` 59 /// def fun(): 60 /// __cdbkernel__ = cadabra.create_empty_scope() 61 /// [your code here] 62 /// ``` 63 /// Note that in all these cases, changes to properties remain local and 64 /// do not leak into the global property list. 65 /// 66 /// All Cadabra algorithms, when called from Python, will first look for a 67 /// kernel on the locals stack (i.e.~what `locals()` produces). If 68 /// there is no kernel available locally, they will then revert to using 69 /// the global kernel. 70 Kernel *create_scope(); 71 Kernel *create_scope_from_global(); 72 Kernel *create_empty_scope(); 73 74 /// \ingroup pythoncore 75 /// 76 /// Get a pointer to the currently visible kernel. 77 Kernel *get_kernel_from_scope(); 78 79 80 void init_kernel(pybind11::module& m); 81 82 } 83