Skip to content

Commit 483294c

Browse files
committed
Add tutorial in the user documentation
1 parent 37dbbe4 commit 483294c

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

docs/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ algebraic systems that is already built in.
115115
The label for boson exchange symmetry.
116116

117117

118+
.. _problem_drudges:
119+
118120
Direct support of different problems
119121
------------------------------------
120122

docs/tutorial.rst

+281
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,284 @@
11
Drudge tutorial for beginners
22
=============================
33

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

Comments
 (0)