1# coding: utf-8
2"""
3Objects to read and analyze optical properties stored in the optic.nc file produced by optic executable.
4"""
5import numpy as np
6import abipy.core.abinit_units as abu
7
8from collections import OrderedDict
9from monty.string import marquee, list_strings
10from monty.functools import lazy_property
11from abipy.core.mixins import AbinitNcFile, Has_Header, Has_Structure, Has_ElectronBands, NotebookWriter
12from abipy.tools.plotting import add_fig_kwargs, get_ax_fig_plt, get_axarray_fig_plt, set_axlims, data_from_cplx_mode
13from abipy.abio.robots import Robot
14from abipy.electrons.ebands import ElectronsReader, RobotWithEbands
15
16ALL_CHIS = OrderedDict([
17    ("linopt", {
18        "longname": "Dielectric function",
19        "rank": 2,
20        #"terms":
21        #"latex": r"\chi(\omega)"
22        }
23    ),
24    ("shg", {
25        "longname": "Second Harmonic Generation",
26        "rank": 3,
27        "terms": ["shg_inter2w", "shg_inter1w", "shg_intra2w",
28                  "shg_intra1w", "shg_intra1wS", "shg_chi2tot"],
29        }
30        #"latex": r"\chi(-2\omega, \omega, \omega)"
31    ),
32    ("leo", {
33        "longname": "Linear Electro-optic",
34        "rank": 3,
35        "terms": ["leo_chi", "leo_eta", "leo_sigma", "leo_chi2tot"],
36        }
37        #"latex": r"\chi(-\omega, \omega, 0)"
38    )
39])
40
41#LEO2_TERMS = OrderedDict([
42#    ("leo2_chiw", None),
43#    ("leo2_etaw", None),
44#    ("leo2_chi2w", None),
45#    ("leo2_eta2w", None),
46#    ("leo2_sigmaw", None),
47#    ("leo2_chi2tot", None),
48#])
49
50
51#####################################
52# Helper functions for linear optic #
53#####################################
54
55def reflectivity(eps):
56    """Reflectivity(w) from vacuum, at normal incidence"""
57    return np.sqrt(0.5 * (np.abs(eps) + eps.real))
58
59
60#def abs_coeff(eps):
61#    """absorption coeff (in m-1) = omega Im(eps) / c n(eps)"""
62#    if (abs(eps(iw)) + dble(eps(iw)) > zero) then
63#       tmpabs = aimag(eps(iw))*ene / sqrt(half*( abs(eps(iw)) + dble(eps(iw)) )) / Sp_Lt / Bohr_meter
64#    end if
65
66
67def kappa(eps):
68    """Im(refractive index(w)) aka kappa"""
69    return np.sqrt(0.5 * (np.abs(eps) - eps.real))
70
71
72def n(eps):
73    """Re(refractive index(w)) aka n"""
74    return np.sqrt(0.5 * (np.abs(eps) + eps.real))
75
76
77#def eels(eps):
78#    np.where(np.abs(eps)
79#    return - (1 / eps).imag
80
81
82LINEPS_WHAT2EFUNC = dict(
83    n=n,
84    reflectivity=reflectivity,
85    kappa=kappa,
86    re=lambda eps: eps.real,
87    im=lambda eps: eps.imag,
88    #abs: lambda: eps: np.abs(eps),
89    #angle: lambda: eps: np.angle(eps, deg=False),
90    #abs_coeff=abs_coeff
91    #eels=lambda: eps /
92)
93
94
95class OpticNcFile(AbinitNcFile, Has_Header, Has_Structure, Has_ElectronBands, NotebookWriter):
96    """
97    This file contains the results produced by optic. Provides methods to plot optical
98    properties and susceptibilty tensors. Used by :class:`OpticRobot` to analyze multiple files.
99
100    Usage example:
101
102    .. code-block:: python
103
104        with OpticNcFile("out_optic.nc") as optic:
105            optic.ebands.plot()
106            optic.plot()
107
108    .. rubric:: Inheritance Diagram
109    .. inheritance-diagram:: OpticNcFile
110    """
111
112    @classmethod
113    def from_file(cls, filepath):
114        """Initialize the object from a netcdf file."""
115        return cls(filepath)
116
117    def __init__(self, filepath):
118        super().__init__(filepath)
119        self.reader = OpticReader(filepath)
120
121        # Read optic input variables and info on k-point sampling and store them in self.
122        keys = [
123            "kptopt",
124            # optic input variables
125            "broadening", "maxomega", "domega", "scissor", "tolerance", "do_antiresonant", "do_ep_renorm",
126        ]
127        for key in keys:
128            setattr(self, key, self.reader.read_value(key))
129
130    @lazy_property
131    def wmesh(self):
132        """
133        Frequency mesh in eV. Note that the same frequency-mesh is used
134        for the different optical properties.
135        """
136        return self.reader.read_value("wmesh")
137
138    def __str__(self):
139        """String representation."""
140        return self.to_string()
141
142    def to_string(self, verbose=0):
143        """String representation."""
144        lines = []; app = lines.append
145
146        app(marquee("File Info", mark="="))
147        app(self.filestat(as_string=True))
148        app("")
149        app(self.structure.to_string(verbose=verbose, title="Structure"))
150        app("")
151        app(self.ebands.to_string(with_structure=False, title="Electronic Bands"))
152
153        app(marquee("Optic calculation", mark="="))
154        # Show Optic variables.
155        app("broadening: %s [Ha], %.3f (eV)" % (self.broadening, self.broadening * abu.Ha_eV))
156        app("scissor: %s [Ha], %.3f (eV)" % (self.scissor, self.scissor * abu.Ha_eV))
157        app("tolerance: %s [Ha], %.3f (eV)" % (self.tolerance, self.tolerance * abu.Ha_eV))
158        app("maxomega: %s [Ha], %.3f (eV)" % (self.maxomega, self.maxomega * abu.Ha_eV))
159        app("domega: %s [Ha], %.3f (eV)" % (self.domega, self.domega * abu.Ha_eV))
160        app("do_antiresonant %s, do_ep_renorm %s" % (self.do_antiresonant, self.do_ep_renorm))
161        app("Number of temperatures: %d" % self.reader.ntemp)
162
163        # Show available tensors and computed components.
164        for key, info in ALL_CHIS.items():
165            if not self.reader.computed_components[key]: continue
166            app("%s components computed: %s" % (
167                info["longname"], ", ".join(self.reader.computed_components[key])))
168
169        if verbose > 1:
170            app(marquee("Abinit Header", mark="="))
171            app(self.hdr.to_string(verbose=verbose))
172
173        return "\n".join(lines)
174
175    @lazy_property
176    def ebands(self):
177        """|ElectronBands| object."""
178        return self.reader.read_ebands()
179
180    @property
181    def structure(self):
182        """|Structure| object."""
183        return self.ebands.structure
184
185    @lazy_property
186    def has_linopt(self):
187        """True if the ncfile contains Second Harmonic Generation tensor."""
188        return "linopt" in self.reader.computed_components
189
190    @lazy_property
191    def has_shg(self):
192        """True if the ncfile contains Second Harmonic Generation tensor."""
193        return "shg" in self.reader.computed_components
194
195    @lazy_property
196    def has_leo(self):
197        """True if the ncfile contains the Linear Electro-optic tensor"""
198        return "leo" in self.reader.computed_components
199
200    #@lazy_property
201    #def xc(self):
202    #    """:class:`XcFunc object with info on the exchange-correlation functional."""
203    #    return self.reader.read_abinit_xcfunc()
204
205    def close(self):
206        """Close the file."""
207        self.reader.close()
208
209    @lazy_property
210    def params(self):
211        """:class:`OrderedDict` with parameters that might be subject to convergence studies."""
212        od = self.get_ebands_params()
213        return od
214
215    @staticmethod
216    def get_linopt_latex_label(what, comp):
217        """
218        Return latex label for linear-optic quantities. Used in plots.
219        """
220        return dict(
221            n=r"$n_{%s}$" % comp,
222            reflectivity=r"$R_{%s}$" % comp,
223            kappa=r"$\kappa_{%s}$" % comp,
224            re=r"$\Re(\epsilon_{%s})$" % comp,
225            im=r"$\Im(\epsilon_{%s})$" % comp,
226            #abs=r"$|\epsilon_{%s}|$" % comp,
227            #abs_coeff=abs_coeff_{%s}} % comp,
228            #eels:r"EELS_{%s}" % comp,
229        )[what]
230
231    def get_chi2_latex_label(self, key, what, comp):
232        """
233        Build latex label for chi2-related quantities. Used in plots.
234        """
235        symb = "{%s}" % key.capitalize()
236        return dict(
237            re=r"$\Re(%s_{%s})$" % (symb, comp),
238            im=r"$\Im(%s_{%s})$" % (symb, comp),
239            abs=r"$|%s_{%s}|$" % (symb, comp),
240        )[what]
241
242    @add_fig_kwargs
243    def plot_linear_epsilon(self, components="all", what="im", itemp=0,
244                            ax=None, xlims=None, with_xlabel=True, label=None, fontsize=12, **kwargs):
245        """
246        Plot components of the linear dielectric function.
247
248        Args:
249            components: List of cartesian tensor components to plot e.g. ["xx", "xy"].
250                "all" if all components available on file should be plotted on the same ax.
251            what: quantity to plot. "re" for real part, "im" for imaginary. Accepts also "abs", "angle".
252            itemp: Temperature index.
253            ax: |matplotlib-Axes| or None if a new figure should be created.
254            xlims: Set the data limits for the x-axis. Accept tuple e.g. ``(left, right)``
255                   or scalar e.g. ``left``. If left (right) is None, default values are used.
256            with_xlabel: True if x-label should be added.
257            label: True to add legend label to each curve.
258            fontsize: Legend and label fontsize.
259
260        Returns: |matplotlib-Figure|
261        """
262        comp2eps = self.reader.read_lineps(components, itemp=itemp)
263
264        ax, fig, plt = get_ax_fig_plt(ax=ax)
265        for comp, eps in comp2eps.items():
266            values = LINEPS_WHAT2EFUNC[what](eps)
267            # Note: I'm skipping the first point at w=0 because optic does not compute it!
268            # The same trick is used in the other plots.
269            ax.plot(self.wmesh[1:], values[1:],
270                    label=self.get_linopt_latex_label(what, comp) if label is None else label)
271
272        ax.grid(True)
273        if with_xlabel: ax.set_xlabel('Photon Energy (eV)')
274        set_axlims(ax, xlims, "x")
275        ax.legend(loc="best", fontsize=fontsize, shadow=True)
276
277        return fig
278
279    @add_fig_kwargs
280    def plot_linopt(self, select="all", itemp=0, xlims=None, **kwargs):
281        """
282        Subplots with all linear optic quantities selected by ``select`` at temperature ``itemp``.
283
284        Args:
285            select:
286            itemp: Temperature index.
287            xlims: Set the data limits for the x-axis. Accept tuple e.g. ``(left, right)``
288                or scalar e.g. ``left``. If left (right) is None, default values are used.
289
290        Returns: |matplotlib-Figure|
291        """
292        key = "linopt"
293        if not self.reader.computed_components[key]: return None
294        if select == "all": select = list(LINEPS_WHAT2EFUNC.keys())
295        select = list_strings(select)
296
297        nrows, ncols = len(select), 1
298        ax_mat, fig, plt = get_axarray_fig_plt(None, nrows=nrows, ncols=ncols,
299                                               sharex=True, sharey=False, squeeze=True)
300
301        components = self.reader.computed_components[key]
302        for i, (what, ax) in enumerate(zip(select, ax_mat)):
303            self.plot_linear_epsilon(what=what, itemp=itemp, components=components,
304                                     ax=ax, xlims=xlims, with_xlabel=(i == len(select) - 1),
305                                     show=False)
306        return fig
307
308    @add_fig_kwargs
309    def plot_chi2(self, key, components="all", what="abs", itemp=0, decompose=False,
310                  ax=None, xlims=None, with_xlabel=True, label=None, fontsize=12, **kwargs):
311        """
312        Low-level function to plot chi2 tensor.
313
314        Args:
315            key:
316            components: List of cartesian tensor components to plot e.g. ["xxx", "xyz"].
317                "all" if all components available on file should be plotted on the same ax.
318            what: quantity to plot. "re" for real part, "im" for imaginary, Accepts also "abs", "angle".
319            itemp: Temperature index.
320            decompose: True to plot individual contributions.
321            ax: |matplotlib-Axes| or None if a new figure should be created.
322            xlims: Set the data limits for the x-axis. Accept tuple e.g. ``(left, right)``
323                   or scalar e.g. ``left``. If left (right) is None, default values are used.
324            with_xlabel: True to add x-label.
325            label: True to add legend label to each curve.
326            fontsize: Legend and label fontsize.
327
328        Returns: |matplotlib-Figure|
329        """
330        if not self.reader.computed_components[key]: return None
331        comp2terms = self.reader.read_tensor3_terms(key, components, itemp=itemp)
332
333        ax, fig, plt = get_ax_fig_plt(ax=ax)
334        for comp, terms in comp2terms.items():
335            for name, values in terms.items():
336                if not decompose and not name.endswith("tot"): continue
337                values = data_from_cplx_mode(what, values)
338                ax.plot(self.wmesh[1:], values[1:],
339                        label=self.get_chi2_latex_label(key, what, comp) if label is None else label,
340                )
341
342        ax.grid(True)
343        if with_xlabel: ax.set_xlabel('Photon Energy (eV)')
344        set_axlims(ax, xlims, "x")
345        ax.legend(loc="best", fontsize=fontsize, shadow=True)
346
347        return fig
348
349    @add_fig_kwargs
350    def plot_shg(self, **kwargs):
351        """Plot Second Harmonic Generation. See plot_chi2 for args supported."""
352        return self.plot_chi2(key="shg", show=False, **kwargs)
353
354    @add_fig_kwargs
355    def plot_leo(self, **kwargs):
356        """Plot Linear Electro-optic. See plot_chi2 for args supported."""
357        return self.plot_chi2(key="leo", show=False, **kwargs)
358
359    def yield_figs(self, **kwargs):  # pragma: no cover
360        """
361        This function *generates* a predefined list of matplotlib figures with minimal input from the user.
362        Used in abiview.py to get a quick look at the results.
363        """
364        if self.has_linopt:
365            yield self.plot_linear_epsilon(what="re", show=False)
366            yield self.plot_linear_epsilon(what="im", show=False)
367            yield self.plot_linopt(show=False)
368        if self.has_shg:
369            yield self.plot_shg(show=False)
370        if self.has_leo:
371            yield self.plot_leo(show=False)
372
373    def write_notebook(self, nbpath=None):
374        """
375        Write a jupyter_ notebook to ``nbpath``. If nbpath is None, a temporay file in the current
376        working directory is created. Return path to the notebook.
377        """
378        nbformat, nbv, nb = self.get_nbformat_nbv_nb(title=None)
379
380        nb.cells.extend([
381            nbv.new_code_cell("optic = abilab.abiopen('%s')" % self.filepath),
382            nbv.new_code_cell("print(optic)"),
383            nbv.new_code_cell("optic.ebands.plot();"),
384        ])
385
386        # Add plot calls if quantities have been computed.
387        for key, info in ALL_CHIS.items():
388            if not self.reader.computed_components[key]: continue
389            pycall = "optic.plot_%s();" % key
390            nb.cells.extend([
391                nbv.new_code_cell(pycall),
392            ])
393
394        return self._write_nb_nbpath(nb, nbpath)
395
396
397class OpticReader(ElectronsReader):
398    """
399    This object reads the results stored in the optic.nc file
400    It provides helper function to access the most important quantities.
401
402    .. rubric:: Inheritance Diagram
403    .. inheritance-diagram:: OpticReader
404    """
405    def __init__(self, filepath):
406        super().__init__(filepath)
407        self.ntemp = self.read_dimvalue("ntemp")
408
409        self.computed_components = OrderedDict()
410        self.computed_ids = OrderedDict()
411        for chiname, info in ALL_CHIS.items():
412            comp_name = chiname + "_components"
413            if comp_name not in self.rootgrp.variables:
414                fi_comps, ids = [], []
415            else:
416                fi_comps = [str(i) for i in self.read_value(comp_name)]
417                if info["rank"] == 2:
418                    ids = [(int(i[0])-1, int(i[1])-1) for i in fi_comps]
419                elif info["rank"] == 3:
420                    ids = [(int(i[0])-1, int(i[1])-1, int(i[2])-1) for i in fi_comps]
421                else:
422                    raise NotImplementedError("rank %s" % info["rank"])
423
424            self.computed_ids[chiname] = ids
425            self.computed_components[chiname] = [abu.itup2s(it) for it in ids]
426
427    def read_lineps(self, components, itemp=0):
428        """
429        Args:
430            components: List of cartesian tensor components to plot e.g. ["xx", "xy"].
431                "all" if all components available on file should be plotted on the same ax.
432            itemp: Temperature index.
433        """
434        # linopt_epsilon has *Fortran* shape [two, nomega, num_comp, ntemp]
435        key = "linopt"
436        if components == "all": components = self.computed_components[key]
437        if not (self.ntemp > itemp >= 0):
438            raise ValueError("Invalid itemp: %s, ntemp: %s" % (itemp, self.ntemp))
439
440        var = self.read_variable("linopt_epsilon")
441        od = OrderedDict()
442        for comp in list_strings(components):
443            try:
444                ijp = self.computed_components[key].index(comp)
445            except ValueError:
446                raise ValueError("epsilon_component %s was not computed" % comp)
447
448            values = var[itemp, ijp]
449            od[comp] = values[:, 0] + 1j * values[:, 1]
450        return od
451
452    def read_tensor3_terms(self, key, components, itemp=0):
453        """
454        Args:
455            key: Name of the netcdf variable to read.
456            components: List of cartesian tensor components to plot e.g. ["xxx", "xyz"].
457                "all" if all components available on file should be plotted on the same ax.
458            itemp: Temperature index.
459
460        Return:
461            :class:`OrderedDict` mapping cartesian components e.g. "xyz" to data dictionary.
462            Individual entries are listed in ALL_CHIS[key]["terms"]
463        """
464        # arrays have Fortran shape [two, nomega, num_comp, ntemp]
465        if components == "all": components = self.computed_components[key]
466        components = list_strings(components)
467        if not (self.ntemp > itemp >= 0):
468            raise ValueError("Invalid itemp: %s, ntemp: %s" % (itemp, self.ntemp))
469
470        od = OrderedDict([(comp, OrderedDict()) for comp in components])
471        for chiname in ALL_CHIS[key]["terms"]:
472            #print("About to read:", chiname)
473            var = self.read_variable(chiname)
474            for comp in components:
475                try:
476                    ijkp = self.computed_components[key].index(comp)
477                except ValueError:
478                    raise ValueError("%s component %s was not computed" % (key, comp))
479                values = var[itemp, ijkp]
480                od[comp][chiname] = values[:, 0] + 1j * values[:, 1]
481        return od
482
483
484class OpticRobot(Robot, RobotWithEbands):
485    """
486    This robot analyzes the results contained in multiple optic.nc files.
487
488    .. rubric:: Inheritance Diagram
489    .. inheritance-diagram:: OpticRobot
490    """
491    EXT = "OPTIC"
492
493    @lazy_property
494    def computed_components_intersection(self):
495        """
496        Dictionary with the list of cartesian tensor components
497        available in each file. Use keys from ALL_CHIS.
498        """
499        od = OrderedDict()
500        for ncfile in self.abifiles:
501            for chiname in ALL_CHIS:
502                comps = ncfile.reader.computed_components[chiname]
503                if chiname not in od:
504                    od[chiname] = comps
505                else:
506                    # Build intersection while preserving order.
507                    od[chiname] = self.ordered_intersection(od[chiname], comps)
508        return od
509
510    @add_fig_kwargs
511    def plot_linopt_convergence(self, components="all", what_list=("re", "im"),
512                                sortby="nkpt", itemp=0, xlims=None, **kwargs):
513        """
514        Plot the convergence of the dielectric function tensor with respect to
515        parameter defined by ``sortby``.
516
517        Args:
518            components: List of cartesian tensor components to plot e.g. ["xx", "xy"].
519                "all" if all components available on file should be plotted on the same ax.
520            what_list: List of quantities to plot. "re" for real part, "im" for imaginary.
521                Accepts also "abs", "angle".
522            sortby: Define the convergence parameter, sort files and produce plot labels. Can be None, string or function.
523                If None, no sorting is performed.
524                If string, it's assumed that the ncfile has an attribute with the same name and getattr is invoked.
525                If callable, the output of callable(ncfile) is used.
526            itemp: Temperature index.
527            xlims: Set the data limits for the x-axis. Accept tuple e.g. ``(left, right)``
528                   or scalar e.g. ``left``. If left (right) is None, default values are used.
529
530        Returns: |matplotlib-Figure|
531        """
532        # Build grid plot: computed tensors along the rows, what_list along columns.
533        key = "linopt"
534        components = self.computed_components_intersection[key]
535
536        nrows, ncols = len(components), len(what_list)
537        ax_mat, fig, plt = get_axarray_fig_plt(None, nrows=nrows, ncols=ncols,
538                                               sharex=True, sharey=False, squeeze=False)
539
540        label_ncfile_param = self.sortby(sortby)
541        for i, comp in enumerate(components):
542            for j, what in enumerate(what_list):
543                ax = ax_mat[i, j]
544                for ifile, (label, ncfile, param) in enumerate(label_ncfile_param):
545
546                    ncfile.plot_linear_epsilon(components=comp, what=what, itemp=itemp, ax=ax,
547                        xlims=xlims, with_xlabel=(i == len(components) - 1),
548                        label="%s %s" % (sortby, param) if not callable(sortby) else str(param),
549                        show=False)
550
551                    if ifile == 0:
552                        ax.set_title(ncfile.get_linopt_latex_label(what, comp))
553
554                if (i, j) != (0, 0):
555                    ax.legend().set_visible(False)
556
557        return fig
558
559    @add_fig_kwargs
560    def plot_shg_convergence(self, **kwargs):
561        """Plot Second Harmonic Generation. See plot_convergence_rank3 for args supported."""
562        if "what_list" not in kwargs: kwargs["what_list"] = ["abs"]
563        return self.plot_convergence_rank3(key="shg", **kwargs)
564
565    @add_fig_kwargs
566    def plot_leo_convergence(self, **kwargs):
567        """Plot Linear electron-optic. See plot_convergence_rank3 for args supported."""
568        if "what_list" not in kwargs: kwargs["what_list"] = ["abs"]
569        return self.plot_convergence_rank3(key="leo", **kwargs)
570
571    @add_fig_kwargs
572    def plot_convergence_rank3(self, key, components="all", itemp=0, what_list=("abs",),
573                               sortby="nkpt", decompose=False, xlims=None, **kwargs):
574        """
575        Plot convergence of arbitrary rank3 tensor. This is a low-level routine used in other plot methods.
576
577        Args:
578            key: Name of the quantity to analyze.
579            components: List of cartesian tensor components to plot e.g. ["xxx", "xyz"].
580                "all" if all components available on file should be plotted on the same ax.
581            itemp: Temperature index.
582            what_list: List of quantities to plot. "re" for real part, "im" for imaginary.
583                Accepts also "abs", "angle".
584            sortby: Define the convergence parameter, sort files and produce plot labels. Can be None, string or function.
585                If None, no sorting is performed.
586                If string, it's assumed that the ncfile has an attribute with the same name and ``getattr`` is invoked.
587                If callable, the output of callable(ncfile) is used.
588            decompose: True to plot individual contributions.
589            xlims: Set the data limits for the x-axis. Accept tuple e.g. ``(left, right)``
590                or scalar e.g. ``left``. If left (right) is None, default values are used.
591
592        Returns: |matplotlib-Figure|
593        """
594        # Build grid plot: computed tensors along the rows, what_list along columns.
595        components = self.computed_components_intersection[key]
596
597        nrows, ncols = len(components), len(what_list)
598        ax_mat, fig, plt = get_axarray_fig_plt(None, nrows=nrows, ncols=ncols,
599                                               sharex=True, sharey=False, squeeze=False)
600
601        label_ncfile_param = self.sortby(sortby)
602        for i, comp in enumerate(components):
603            for j, what in enumerate(what_list):
604                ax = ax_mat[i, j]
605                for ifile, (label, ncfile, param) in enumerate(label_ncfile_param):
606
607                    ncfile.plot_chi2(key=key, components=comp, what=what, itemp=itemp, decompose=decompose,
608                        ax=ax, xlims=xlims, with_xlabel=(i == len(components) - 1),
609                        label="%s %s" % (sortby, param) if not callable(sortby) else str(param),
610                        show=False, **kwargs)
611
612                    if ifile == 0:
613                        ax.set_title(ncfile.get_chi2_latex_label(key, what, comp))
614
615                if (i, j) != (0, 0):
616                    ax.legend().set_visible(False)
617
618        return fig
619
620    def yield_figs(self, **kwargs):  # pragma: no cover
621        """
622        This function *generates* a predefined list of matplotlib figures with minimal input from the user.
623        Used in abiview.py to get a quick look at the results.
624        """
625        for key, comps in self.computed_components_intersection.items():
626            if not comps: continue
627            plot_fig = getattr(self, "plot_%s_convergence" % key)
628            yield plot_fig(show=False)
629
630    def write_notebook(self, nbpath=None):
631        """
632        Write a jupyter_ notebook to nbpath. If ``nbpath`` is None, a temporay file in the current
633        working directory is created. Return path to the notebook.
634        """
635        nbformat, nbv, nb = self.get_nbformat_nbv_nb(title=None)
636
637        args = [(l, f.filepath) for l, f in self.items()]
638        nb.cells.extend([
639            #nbv.new_markdown_cell("# This is a markdown cell"),
640            nbv.new_code_cell("robot = abilab.OpticRobot(*%s)\nrobot.trim_paths()\nrobot" % str(args)),
641        ])
642
643        for key, comps in self.computed_components_intersection.items():
644            if not comps: continue
645            pycall = "robot.plot_%s_convergence();" % key
646            nb.cells.extend([
647                nbv.new_code_cell(pycall),
648            ])
649
650        # Mixins
651        nb.cells.extend(self.get_baserobot_code_cells())
652        nb.cells.extend(self.get_ebands_code_cells())
653
654        return self._write_nb_nbpath(nb, nbpath)
655