|
1 | 1 | Drudge tutorial for beginners
|
2 | 2 | =============================
|
3 | 3 |
|
| 4 | +.. currentmodule:: drudge |
| 5 | + |
| 6 | + |
| 7 | +Get started |
| 8 | +----------- |
| 9 | + |
| 10 | + |
| 11 | +Drudge is a library built on top of the SymPy computer algebra library for |
| 12 | +noncommutative and tensor alegbras. Usually for these style of problems, the |
| 13 | +symbolic manipulation and simplification of mathematical expressions requires a |
| 14 | +lot of context-dependent information, like the specific commutation rules and |
| 15 | +things like the dummy symbols to be used for different ranges. So the primary |
| 16 | +entry point for using the library is the :py:class:`Drudge` class, which serves |
| 17 | +as a central repository of all kinds of domain-specific informations. To |
| 18 | +create a drudge instance, we need to give it a Spark context so that it is |
| 19 | +capable of parallelize things. For instance, to run things locally with all |
| 20 | +available cores, we can do |
| 21 | + |
| 22 | +.. doctest:: |
| 23 | + |
| 24 | + >>> import pyspark |
| 25 | + >>> import drudge |
| 26 | + >>> ctx = pyspark.SparkContext('local[*]', 'drudge-tutorial') |
| 27 | + >>> dr = drudge.Drudge(ctx) |
| 28 | + |
| 29 | +Then from it, we can create the symbolic expressions as :py:class:`Tensor` |
| 30 | +objects, which are basically mathematical expressions containing noncommutative |
| 31 | +objects and symbolic summations. For the noncommutativity, in spite of the |
| 32 | +availability of some basic support of it in SymPy, here we have the |
| 33 | +:py:class:`Vec` class to specifically designate the noncommutativity of its |
| 34 | +multiplication. It can be created with a label and indexed with SymPy |
| 35 | +expressions. |
| 36 | + |
| 37 | +.. doctest:: |
| 38 | + |
| 39 | + >>> v = drudge.Vec('v') |
| 40 | + >>> import sympy |
| 41 | + >>> a = sympy.Symbol('a') |
| 42 | + >>> str(v[a]) |
| 43 | + 'v[a]' |
| 44 | + |
| 45 | +For the symbolic summations, we have the :py:class:`Range` class, which denotes |
| 46 | +a symbolic set that a variable could be summed over. It can be created by just |
| 47 | +a label. |
| 48 | + |
| 49 | +.. doctest:: |
| 50 | + |
| 51 | + >>> l = drudge.Range('L') |
| 52 | + |
| 53 | +With these, we can create tensor objects by using the :py:meth:`Drudge.sum` |
| 54 | +method, |
| 55 | + |
| 56 | +.. doctest:: |
| 57 | + |
| 58 | + >>> x = sympy.IndexedBase('x') |
| 59 | + >>> tensor = dr.sum((a, l), x[a] * v[a]) |
| 60 | + >>> str(tensor) |
| 61 | + 'sum_{a} x[a] * v[a]' |
| 62 | + |
| 63 | +Now we got a symbolic tensor of a sum of vectors modulated by a SymPy |
| 64 | +IndexedBase. Actually any type of SymPy expression can be used to modulate the |
| 65 | +noncommutative vectors. |
| 66 | + |
| 67 | +.. doctest:: |
| 68 | + |
| 69 | + >>> tensor = dr.sum((a, l), sympy.sin(a) * v[a]) |
| 70 | + >>> str(tensor) |
| 71 | + 'sum_{a} sin(a) * v[a]' |
| 72 | + |
| 73 | +And we can also have multiple summations and product of the vectors. |
| 74 | + |
| 75 | +.. doctest:: |
| 76 | + |
| 77 | + >>> b = sympy.Symbol('b') |
| 78 | + >>> tensor = dr.sum((a, l), (b, l), x[a, b] * v[a] * v[b]) |
| 79 | + >>> str(tensor) |
| 80 | + 'sum_{a, b} x[a, b] * v[a] * v[b]' |
| 81 | + |
| 82 | +Of cause the multiplication of the vectors will not be commutative, |
| 83 | + |
| 84 | +.. doctest:: |
| 85 | + |
| 86 | + >>> tensor = dr.sum((a, l), (b, l), x[a, b] * v[b] * v[a]) |
| 87 | + >>> str(tensor) |
| 88 | + 'sum_{a, b} x[a, b] * v[b] * v[a]' |
| 89 | + |
| 90 | +Normally, for each symbolic range, we have some traditional symbols used as |
| 91 | +dummies for summations over them, giving these information to drudge objects |
| 92 | +can be very helpful. Here in this demonstration, we can use the |
| 93 | +:py:meth:`Drudge.set_dumms` method. |
| 94 | + |
| 95 | +.. doctest:: |
| 96 | + |
| 97 | + >>> dr.set_dumms(l, sympy.symbols('a b c d')) |
| 98 | + [a, b, c, d] |
| 99 | + >>> dr.add_resolver_for_dumms() |
| 100 | + |
| 101 | +where the call to the :py:meth:`Drudge.add_resolver_for_dumms` method could |
| 102 | +tell the drudge to interpret all the dummy symbols to be over the range that |
| 103 | +they are set to. By giving drudge object such domain-specific information, we |
| 104 | +can have a lot convenience. For instance, now we can use Einstein summation |
| 105 | +convention to create tensor object, without the need to spell all the |
| 106 | +summations out. |
| 107 | + |
| 108 | +.. doctest:: |
| 109 | + |
| 110 | + >>> tensor = dr.einst(x[a, b] * v[a] * v[b]) |
| 111 | + >>> str(tensor) |
| 112 | + 'sum_{a, b} x[a, b] * v[a] * v[b]' |
| 113 | + |
| 114 | +Also the drudge knows what to do when more dummies are needed in mathematical |
| 115 | +operations. For instance, when we multiply things, |
| 116 | + |
| 117 | +.. doctest:: |
| 118 | + |
| 119 | + >>> tensor = dr.einst(x[a] * v[a]) |
| 120 | + >>> prod = tensor * tensor |
| 121 | + >>> str(prod) |
| 122 | + 'sum_{a, b} x[a]*x[b] * v[a] * v[b]' |
| 123 | + |
| 124 | +Here the dummy :math:`b` is automatically used since the drudge object knows |
| 125 | +available dummies for its range. Also the range and the dummies are |
| 126 | +automatically added to the name archive of the drudge, which can be access by |
| 127 | +:py:attr:`Drudge.names`. |
| 128 | + |
| 129 | +.. doctest:: |
| 130 | + |
| 131 | + >>> p = dr.names |
| 132 | + >>> p.L |
| 133 | + Range('L') |
| 134 | + >>> p.L_dumms |
| 135 | + [a, b, c, d] |
| 136 | + >>> p.d |
| 137 | + d |
| 138 | + |
| 139 | +Here in this example, we set the dummies ourselves by |
| 140 | +:py:meth:`Drudge.set_dumms`. Normally, in subclasses of :py:class:`Drudge` for |
| 141 | +different specific problems, such setting up is already finished within the |
| 142 | +class. We can just directly get what we need from the names archive. There is |
| 143 | +also a method :py:meth:`Drudge.inject_names` for the convenience of interactive |
| 144 | +work. |
| 145 | + |
| 146 | + |
| 147 | +Tensor manipulations |
| 148 | +-------------------- |
| 149 | + |
| 150 | + |
| 151 | +Now with tensors created by :py:meth:`Drudge.sum` or :py:meth:`Drudge.einst`, a |
| 152 | +lot of mathematical operations are available to them. In addition to the |
| 153 | +above example of (noncommutative) multiplication, we can also have the linear |
| 154 | +algebraic operations of addition and scalar multiplication. |
| 155 | + |
| 156 | +.. doctest:: |
| 157 | + |
| 158 | + >>> tensor = dr.einst(x[a] * v[a]) |
| 159 | + >>> y = sympy.IndexedBase('y') |
| 160 | + >>> res = tensor + dr.einst(y[a] * v[a]) |
| 161 | + >>> str(res) |
| 162 | + 'sum_{a} x[a] * v[a]\n + sum_{a} y[a] * v[a]' |
| 163 | + |
| 164 | + >>> res = 2 * tensor |
| 165 | + >>> str(res) |
| 166 | + 'sum_{a} 2*x[a] * v[a]' |
| 167 | + |
| 168 | +We can also perform some complex substitutions on either the vector or the |
| 169 | +amplitude part, by using the :py:meth:`Drudge.subst` method. |
| 170 | + |
| 171 | +.. doctest:: |
| 172 | + |
| 173 | + >>> t = sympy.IndexedBase('t') |
| 174 | + >>> w = drudge.Vec('w') |
| 175 | + >>> substed = tensor.subst(v[a], dr.einst(t[a, b] * w[b])) |
| 176 | + >>> str(substed) |
| 177 | + 'sum_{a, b} x[a]*t[a, b] * w[b]' |
| 178 | + |
| 179 | + >>> substed = tensor.subst(x[a], sympy.sin(a)) |
| 180 | + >>> str(substed) |
| 181 | + 'sum_{a} sin(a) * v[a]' |
| 182 | + |
| 183 | +Note that here the substituted vector does not have to match the left-hand side |
| 184 | +of the substitution exactly, pattern matching is done here. Other mathematical |
| 185 | +operations are also available, like symbolic differentiation by |
| 186 | +:py:meth:`Tensor.diff` and commutation by ``|`` operator |
| 187 | +:py:meth:`Tensor.__or__`. |
| 188 | + |
| 189 | +Usually for tensorial problems, full simplification requires the utilization of |
| 190 | +some symmetries present on the indexed quantities by permutations among their |
| 191 | +indices. For instance, an anti-symmetric matrix entry changes sign when we |
| 192 | +transpose the two indices. Such information can be told to drudge by using the |
| 193 | +:py:meth:`Drudge.set_symm` method, by giving generators of the symmetry group |
| 194 | +by :py:class:`Perm` instances. For instance, we can do, |
| 195 | + |
| 196 | +.. testcode:: |
| 197 | + |
| 198 | + dr.set_symm(x, drudge.Perm([1, 0], drudge.NEG)) |
| 199 | + |
| 200 | +Then the master simplification algorithm in :py:meth:`Tensor.simplify` is able |
| 201 | +to take full advantage of such information. |
| 202 | + |
| 203 | +.. doctest:: |
| 204 | + |
| 205 | + >>> tensor = dr.einst(x[a, b] * v[a] * v[b] + x[b, a] * v[a] * v[b]) |
| 206 | + >>> str(tensor) |
| 207 | + 'sum_{a, b} x[a, b] * v[a] * v[b]\n + sum_{a, b} x[b, a] * v[a] * v[b]' |
| 208 | + >>> str(tensor.simplify()) |
| 209 | + '0' |
| 210 | + |
| 211 | +Normally, drudge subclasses for specific problems add symmetries for some |
| 212 | +important indexed bases in the problem. And some drudge subclasses have helper |
| 213 | +methods for the setting of such symmetries, like |
| 214 | +:py:meth:`FockDrudge.set_n_body_base` and :py:meth:`FockDrudge.set_dbbar_base`. |
| 215 | + |
| 216 | +For the simplification of the noncommutative vector parts, the base |
| 217 | +:py:class:`Drudge` class does **not** consider any commutation rules among the |
| 218 | +vectors. It works on the free algebra, while the subclasses could have the |
| 219 | +specific commutation rules added for the algebraic system. For instance, |
| 220 | +:py:class:`WickDrudge` add abstract commutation rules where all the commutators |
| 221 | +have scalar values. Based on it, its special subclass :py:class:`FockDrudge` |
| 222 | +implements the canonical commutation relations for bosons and the canonical |
| 223 | +anti-commutation relations for fermions. |
| 224 | + |
| 225 | +These drudge subclasses only has the mathematical commutation rules implemented, |
| 226 | +for convenience in solving problems, many drudge subclasses are built-in with a |
| 227 | +lot of domain-specific information like the ranges and dummies, which are listed |
| 228 | +in :ref:`problem_drudges`. For instance, we can easily see the commutativity of |
| 229 | +two particle-hole excitation operators by using the :py:class:`PartHoleDrudge`. |
| 230 | + |
| 231 | + |
| 232 | +.. doctest:: |
| 233 | + |
| 234 | + >>> phdr = drudge.PartHoleDrudge(ctx) |
| 235 | + >>> t = sympy.IndexedBase('t') |
| 236 | + >>> u = sympy.IndexedBase('u') |
| 237 | + >>> p = phdr.names |
| 238 | + >>> a, i = p.a, p.i |
| 239 | + >>> excit1 = phdr.einst(t[a, i] * p.c_dag[a] * p.c_[i]) |
| 240 | + >>> excit2 = phdr.einst(u[a, i] * p.c_dag[a] * p.c_[i]) |
| 241 | + >>> comm = excit1 | excit2 |
| 242 | + >>> str(comm) |
| 243 | + 'sum_{i, a, j, b} t[a, i]*u[b, j] * c[CR, a] * c[AN, i] * c[CR, b] * c[AN, j]\n + sum_{i, a, j, b} -t[a, i]*u[b, j] * c[CR, b] * c[AN, j] * c[CR, a] * c[AN, i]' |
| 244 | + >>> str(comm.simplify()) |
| 245 | + '0' |
| 246 | + |
| 247 | +Note that here basically all things related to the problem, like the vector for |
| 248 | +creation and annihilation operator, the conventional dummies :math:`a` and |
| 249 | +:math:`i` for particle and hole labels, are directly read from the name archive |
| 250 | +of the drudge. Problem-specific drudges are supposed to give such convenience. |
| 251 | + |
| 252 | +In addition to providing context-dependent information for general tensor |
| 253 | +operations, drudge subclasses could also provide additional operations on |
| 254 | +tensors created from them. For instance, for the above commutator, we can |
| 255 | +directly compute the expectation value with respect to the Fermi vacuum by |
| 256 | + |
| 257 | +.. doctest:: |
| 258 | + |
| 259 | + >>> str(comm.eval_fermi_vev()) |
| 260 | + '0' |
| 261 | + |
| 262 | +These additional operations are called tensor methods and are documented in the |
| 263 | +drudge subclasses. |
| 264 | + |
| 265 | + |
| 266 | +Examples on real-world applications |
| 267 | +----------------------------------- |
| 268 | + |
| 269 | +In this tutorial, some simple examples are run directly inside a Python |
| 270 | +interpreter. Actually drudge is designed to work well inside Jupyter |
| 271 | +notebooks. By calling the :py:meth:`Tensor.display` method, tensor objects can |
| 272 | +be mathematically displayed in Jupyter sessions. An example of interactive |
| 273 | +usage of drudge, we have a `sample notebook`_ in ``docs/examples/ccsd.ipynb`` |
| 274 | +in the project source. Also included is a `general script`_ ``gencc.py`` for |
| 275 | +the automatic derivation of coupled-cluster theories, mostly to demonstrate |
| 276 | +using drudge programmatically. And we also have a `script for RCCSD theory`_ |
| 277 | +to demonstrate its usage in large-scale spin-explicit coupled-cluster theories. |
| 278 | + |
| 279 | + |
| 280 | +.. _sample notebook: https://github.com/tschijnmo/drudge/blob/master/docs/examples/ccsd.ipynb |
| 281 | + |
| 282 | +.. _general script: https://github.com/tschijnmo/drudge/blob/master/docs/examples/gencc.py |
| 283 | + |
| 284 | +.. _script for RCCSD theory: https://github.com/tschijnmo/drudge/blob/master/docs/examples/rccsd.py |
0 commit comments