diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a39409e759c75fac8d42c61e4a6f941fcc9e1dea..302a872077f5eaaa8d2616cd4644ed30e953f65d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,4 +29,4 @@ build and upload the contents: - mkdocs build - "rsync -rv site/* solidstate@tnw-tn1.tudelft.net:" only: - - branches@solidstate/lectures + - master@solidstate/lectures diff --git a/code/bands_2d.py b/code/bands_2d.py deleted file mode 100644 index a8bf92872e415aac8ca1e00ba178ce8779baaf3c..0000000000000000000000000000000000000000 --- a/code/bands_2d.py +++ /dev/null @@ -1,124 +0,0 @@ -import numpy as np -import plotly.offline as py -import plotly.graph_objs as go - - -pi = np.pi - -def E(k_x, k_y): - delta = np.array([-2*pi, 0, 2*pi]) - H = np.diag( - ((k_x + delta)[:, np.newaxis]**2 - + (k_y + delta)[np.newaxis]**2).flatten() - ) - return tuple(np.linalg.eigvalsh(H + 5)[:3]) - -E = np.vectorize(E, otypes=(float, float, float)) - - -figure_html = """ -<head> -<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> -</head> -<body>{figure} -</body> -""".format - - -def plot_nfem(): - momenta = np.linspace(-2*pi, 2*pi, 100) - kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :] - bands = E(kx, ky) - - # Extended Brillouin zone scheme - pad = .3 - first_BZ = ((abs(kx) < pi + pad) & (abs(ky) < pi + pad)) - second_BZ = ( - ((abs(kx) > pi - pad) | (abs(ky) > pi - pad)) - & ((abs(kx + ky) < 2*pi + pad) & (abs(kx - ky) < 2*pi + pad)) - ) - third_BZ = ( - (abs(kx + ky) > 2*pi - pad) | (abs(kx - ky) > 2*pi - pad) - ) - - bands[0][~first_BZ] = np.nan - bands[1][~second_BZ] = np.nan - #bands[2][~third_BZ] = np.nan - - # Actually plotting - - fig = go.Figure( - data = [ - go.Surface( - z=band / 5, - colorscale=color, - opacity=opacity, - showscale=False, - hoverinfo=False, - x=momenta, - y=momenta, - ) - for band, color, opacity - in zip(bands[:2], - ['#cf483d', '#3d88cf'], - (1, 0.9)) - ], - layout = go.Layout( - title='Nearly free electrons in 2D', - autosize=True, - width=500, - height=500, - hovermode=False, - scene=dict( - yaxis={"title": "k_y"}, - xaxis={"title": "k_x"}, - zaxis={"title": "E"}, - ) - ) - ) - with open('src/figures/nfem_2d.html', 'w') as f: - f.write(figure_html(figure= - py.plot(fig, show_link=False, - output_type='div', include_plotlyjs=False) - )) - - -def plot_tb(): - momenta = np.linspace(-pi, pi, 100) - kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :] - energies = -np.cos(kx) - np.cos(ky) - fig = go.Figure( - data = [ - go.Surface( - z=energies, - colorscale='#3d88cf', - opacity=1, - showscale=False, - hoverinfo=False, - x=momenta, - y=momenta, - ) - ], - layout = go.Layout( - title='Tight-binding in 2D', - autosize=True, - width=500, - height=500, - hovermode=False, - scene=dict( - yaxis={"title": "k_y"}, - xaxis={"title": "k_x"}, - zaxis={"title": "E"}, - ) - ) - ) - with open('src/figures/tb_2d.html', 'w') as f: - f.write(figure_html(figure= - py.plot(fig, show_link=False, - output_type='div', include_plotlyjs=False) - )) - - -if __name__ == '__main__': - plot_nfem() - plot_tb() diff --git a/execute.py b/execute.py index 71c7b23ea2d2bffa62a0ec3efd43f7a936c2ccdc..826a279e74606c99db6f1760d136aa43d1ade0a0 100644 --- a/execute.py +++ b/execute.py @@ -1,27 +1,52 @@ from pathlib import Path import shutil +import mimetypes import nbconvert import notedown from traitlets.config import Config +from nbconvert_fix import ExtractOutputPreprocessor + + reader = notedown.MarkdownReader() +mimetypes.add_type('application/vnd.plotly.v1+json', '.json') + src = Path('src') target = Path('docs') shutil.rmtree(target, ignore_errors=True) target.mkdir(exist_ok=True) shutil.copytree(src / 'figures', target / 'figures') +output_extractor = ExtractOutputPreprocessor() +output_extractor.extract_output_types = ( + output_extractor.extract_output_types + | {'application/vnd.plotly.v1+json'} +) + exporter = nbconvert.MarkdownExporter( config=Config(dict( MarkdownExporter=dict( preprocessors=[ nbconvert.preprocessors.ExecutePreprocessor, - nbconvert.preprocessors.ExtractOutputPreprocessor, + output_extractor, ], - exclude_input=True - ) + exclude_input=True, + template_file='extra_markdown.tpl', + ), + NbConvertBase=dict( + display_data_priority=[ + 'text/html', + 'text/markdown', + 'image/svg+xml', + 'text/latex', + 'image/png', + 'application/vnd.plotly.v1+json', + 'image/jpeg', + 'text/plain' + ] + ), )) ) diff --git a/extra_markdown.tpl b/extra_markdown.tpl new file mode 100644 index 0000000000000000000000000000000000000000..24c58ba066f31bf340d29cfa3292a9547c0fc776 --- /dev/null +++ b/extra_markdown.tpl @@ -0,0 +1,20 @@ +{% extends 'markdown.tpl' %} + +{% set plotly_counter = 1 %} + +{%- block data_other -%} +{%- for type in output.data | filter_data_type -%} +{%- if type == 'application/vnd.plotly.v1+json' -%} +{% set plotly_url = output.metadata.filenames['application/vnd.plotly.v1+json'] | path2url %} +<div id="plotly-{{plotly_counter}}"></div> + +<script> +window.addEventListener('load', function() { + Plotly.d3.json('/{{plotly_url}}', function(error, fig) { + Plotly.plot('plotly-{{plotly_counter}}', fig.data, fig.layout); + }); +}); +</script> +{%- endif -%} +{%- endfor -%} +{%- endblock -%} diff --git a/mkdocs.yml b/mkdocs.yml index 950a5bd42ae7cc3455a60f3bff0e2637711ad97b..9a93ea2cf403b79f0cc724bf552ab704f77d97e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,5 +33,5 @@ markdown_extensions: extra_javascript: - 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/MathJax.js?config=TeX-AMS_HTML' - + - 'https://cdn.plot.ly/plotly-latest.min.js' copyright: "Copyright © 2017-2018 Delft University of Technology, CC-BY-SA-NC 4.0." diff --git a/nbconvert_fix.py b/nbconvert_fix.py new file mode 100644 index 0000000000000000000000000000000000000000..65f94101d05e9c70fb6ec63e974e8a9a51e269d7 --- /dev/null +++ b/nbconvert_fix.py @@ -0,0 +1,102 @@ +import sys +import os +import json +from mimetypes import guess_extension +from binascii import a2b_base64 + +from nbformat.notebooknode import NotebookNode +import nbconvert +from nbconvert.preprocessors.extractoutput import guess_extension_without_jpe + + +class ExtractOutputPreprocessor(nbconvert.preprocessors.ExtractOutputPreprocessor): + # Patches against gh-846 following + # https://github.com/jupyter/nbconvert/pull/847 + + def preprocess_cell(self, cell, resources, cell_index): + """ + Apply a transformation on each cell, + + Parameters + ---------- + cell : NotebookNode cell + Notebook cell being processed + resources : dictionary + Additional resources used in the conversion process. Allows + preprocessors to pass variables into the Jinja engine. + cell_index : int + Index of the cell being processed (see base.py) + """ + + #Get the unique key from the resource dict if it exists. If it does not + #exist, use 'output' as the default. Also, get files directory if it + #has been specified + unique_key = resources.get('unique_key', 'output') + output_files_dir = resources.get('output_files_dir', None) + + #Make sure outputs key exists + if not isinstance(resources['outputs'], dict): + resources['outputs'] = {} + + #Loop through all of the outputs in the cell + for index, out in enumerate(cell.get('outputs', [])): + if out.output_type not in {'display_data', 'execute_result'}: + continue + #Get the output in data formats that the template needs extracted + for mime_type in self.extract_output_types: + if mime_type in out.data: + data = out.data[mime_type] + + # If data was JSON, it will become a NotebookNode at this point. + if isinstance(data, NotebookNode): + data = json.dumps(data) + + #Binary files are base64-encoded, SVG is already XML + if mime_type in {'image/png', 'image/jpeg', 'application/pdf'}: + # data is b64-encoded as text (str, unicode), + # we want the original bytes + data = a2b_base64(data) + elif sys.platform == 'win32': + data = data.replace('\n', '\r\n').encode("UTF-8") + else: + data = data.encode("UTF-8") + + ext = guess_extension_without_jpe(mime_type) + if ext is None: + ext = '.' + mime_type.rsplit('/')[-1] + if out.metadata.get('filename', ''): + filename = out.metadata['filename'] + if not filename.endswith(ext): + filename+=ext + else: + filename = self.output_filename_template.format( + unique_key=unique_key, + cell_index=cell_index, + index=index, + extension=ext) + + # On the cell, make the figure available via + # cell.outputs[i].metadata.filenames['mime/type'] + # where + # cell.outputs[i].data['mime/type'] contains the data + if output_files_dir is not None: + filename = os.path.join(output_files_dir, filename) + out.metadata.setdefault('filenames', {}) + out.metadata['filenames'][mime_type] = filename + + if filename in resources['outputs']: + raise ValueError( + "Your outputs have filename metadata associated " + "with them. Nbconvert saves these outputs to " + "external files using this filename metadata. " + "Filenames need to be unique across the notebook, " + "or images will be overwritten. The filename {} is " + "associated with more than one output. The second " + "output associated with this filename is in cell " + "{}.".format(filename, cell_index) + ) + #In the resources, make the figure available via + # resources['outputs']['filename'] = data + resources['outputs'][filename] = data + + return cell, resources diff --git a/src/lecture_6.md b/src/lecture_6.md index 7823068afc6f0db186b4c3f1d395c7e49301e677..d0f686ff6963af320b9aae30f5ab8d6f65a7079d 100644 --- a/src/lecture_6.md +++ b/src/lecture_6.md @@ -1,3 +1,11 @@ +```python +import numpy as np +import plotly.offline as py +import plotly.graph_objs as go + +pi = np.pi +``` + # Tight binding and nearly free electrons Let's summarize what we learned about electrons so far: @@ -58,7 +66,7 @@ The band gaps open where two copies of the free electron dispersion cross. Let's focus on the first crossing. The momentum near it is $k = \pi/a + \delta k$ and we have two copies of the original band structure coming together. One with $\psi_+ \propto e^{i\pi x/a}$, another with $\psi_- \propto e^{-i\pi x/a}$. Near the crossing the wave function is the linear superposition of $\psi_+$ and $\psi_-$: $\psi = \alpha \psi_+ + \beta \psi_-$. We actually used almost the same form of the wave function in LCAO, except instead of $\psi_\pm$ we used the orbitals $\phi_1$ and $\phi_2$ there. Without the lattice potential we can approximate the Hamiltonian of these two states as follows: -$$H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = +$$H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = \begin{pmatrix} E_0 + v \hbar \delta k & 0 \\ 0 & E_0 - v \hbar \delta k\end{pmatrix} \begin{pmatrix}\alpha \\ \beta \end{pmatrix}. $$ @@ -73,7 +81,7 @@ Without $V(x)$ the two wave functions $\psi_+$ and $\psi_-$ are independent sinc So in presence of $V(x)$ the Hamiltonian becomes $$ -H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = +H\begin{pmatrix}\alpha \\ \beta \end{pmatrix} = \begin{pmatrix} E_0 + v \hbar \delta k & W \\ W^* & E_0 - v \hbar \delta k\end{pmatrix} \begin{pmatrix}\alpha \\ \beta \end{pmatrix}, $$ @@ -174,7 +182,74 @@ Sequence of steps (same procedure as in 1D, but harder because of the need to im The resulting band structure looks like this (in the extended Brillouin zone scheme): -<iframe width="100%", height="600" src="../figures/nfem_2d.html" frameBorder="0" align="center" style="border:0;">Your browser still doesn't support iframes??</iframe> + +```python +def E(k_x, k_y): + delta = np.array([-2*pi, 0, 2*pi]) + H = np.diag( + ((k_x + delta)[:, np.newaxis]**2 + + (k_y + delta)[np.newaxis]**2).flatten() + ) + return tuple(np.linalg.eigvalsh(H + 5)[:3]) + +E = np.vectorize(E, otypes=(float, float, float)) + +momenta = np.linspace(-2*pi, 2*pi, 100) +kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :] +bands = E(kx, ky) + +# Extended Brillouin zone scheme +pad = .3 +first_BZ = ((abs(kx) < pi + pad) & (abs(ky) < pi + pad)) +second_BZ = ( + ((abs(kx) > pi - pad) | (abs(ky) > pi - pad)) + & ((abs(kx + ky) < 2*pi + pad) & (abs(kx - ky) < 2*pi + pad)) +) +third_BZ = ( + (abs(kx + ky) > 2*pi - pad) | (abs(kx - ky) > 2*pi - pad) +) + +bands[0][~first_BZ] = np.nan +bands[1][~second_BZ] = np.nan +#bands[2][~third_BZ] = np.nan + +# Actually plotting + +fig = go.Figure( + data = [ + go.Surface( + z=band / 5, + # colorscale=color, + opacity=opacity, + showscale=False, + hoverinfo='none', + x=momenta, + y=momenta, + ) + for band, opacity + in zip(bands[:2], + # ['#cf483d', '#3d88cf'], + (1, 0.9)) + ], + layout = go.Layout( + title='Nearly free electrons in 2D', + autosize=True, + hovermode=False, + margin=dict( + t=50, + l=20, + r=20, + b=50, + ), + scene=dict( + yaxis={"title": "k_y"}, + xaxis={"title": "k_x"}, + zaxis={"title": "E"}, + ) + ) +) +py.iplot(fig, show_link=False) +``` Observe that the top of the first band is above the bottom of the lowest band. Therefore if $V$ is sufficiently weak, the material can be conducting even with 2 electrons per unit cell! @@ -183,7 +258,41 @@ Let's compare the almost parabolic dispersion of the nearly free electron model We now have a dispersion relation $E = E_0 + 2t(\cos k_x a + \cos k_y a)$, which looks like this: -<iframe width="100%", height="600" src="../figures/tb_2d.html" frameBorder="0" align="center" style="border:0;">Your browser still doesn't support iframes??</iframe> +```python +momenta = np.linspace(-pi, pi, 100) +kx, ky = momenta[:, np.newaxis], momenta[np.newaxis, :] +energies = -np.cos(kx) - np.cos(ky) +fig = go.Figure( + data = [ + go.Surface( + z=energies, + # colorscale='#3d88cf', + opacity=1, + showscale=False, + hoverinfo='none', + x=momenta, + y=momenta, + ) + ], + layout = go.Layout( + title='Tight-binding in 2D', + autosize=True, + hovermode=False, + margin=dict( + t=50, + l=20, + r=20, + b=50, + ), + scene=dict( + yaxis={"title": "k_y"}, + xaxis={"title": "k_x"}, + zaxis={"title": "E"}, + ) + ) +) +py.iplot(fig, show_link=False) +``` ### Light adsorption