예제 #1
0
파일: out.py 프로젝트: sofiasanz/sisl
 def readE(itt):
     nonlocal name_conv
     # read the energy tables
     f = self.step_to("Free energy of the ion-electron system",
                      reread=False)[0]
     if not f:
         return None
     next(itt)  # -----
     line = next(itt)
     E = PropertyDict()
     while "----" not in line:
         key, *v = line.split()
         if key == "PAW":
             E[f"{name_conv[key]}1"] = float(v[-2])
             E[f"{name_conv[key]}2"] = float(v[-1])
         else:
             E[name_conv[key]] = float(v[-1])
         line = next(itt)
     line = next(itt)
     E["free"] = float(line.split()[-2])
     line = next(itt)
     line = next(itt)
     v = line.split()
     E["total"] = float(v[4])
     E["sigma0"] = float(v[-1])
     return E
예제 #2
0
    def reset(self, out=None, norm=None):
        """ Reset data table to be able to restart """
        # While this *could* be a named-tuple, we would not be able
        # to override the attribute, hence we use a property dict
        # same effect.
        self.data = PropertyDict(x=[], y=[], hash=[])

        # log
        log = ""

        if not out is None:
            log += f" out={str(out)}"
            self.out = Path(out)

        if not norm is None:
            log += f" norm={str(norm)}"
            if isinstance(norm, str):
                self.norm = (norm, 1.)
            elif isinstance(norm, Real):
                self.norm = ("l2", norm)
            else:
                self.norm = norm
        _log.info(f"{self.__class__.__name__} resetting{log}")
예제 #3
0
파일: _atom.py 프로젝트: tfrederiksen/sisl
class AtomInput:
    """Input for the ``atom`` program see `[1]`_

    This class enables the construction of the ``INP`` file to be fed to ``atom``.


    # Example input for ATOM
    #
    #  Comments allowed here
    #
    #   ae Si ground state all-electron
    #   Si   car
    #       0.0
    #    3    2
    #    3    0      2.00      0.00
    #    3    1      2.00      0.00
    #
    # Comments allowed here
    #
    #2345678901234567890123456789012345678901234567890      Ruler

    .. [1]: https://siesta.icmab.es/SIESTA_MATERIAL/Pseudos/atom_licence.html
    """
    def __init__(self,
                 atom,
                 define=('NEW_CC', 'FREE_FORMAT_RC_INPUT', 'NO_PS_CUTOFFS'),
                 **opts):
        # opts = {
        #   "flavor": "tm2",
        #   "xc": "pb",
        #  optionally libxc
        #   "equation": "r",
        #   "logr": 2.
        #   "cc": False,
        #   "rcore": 2.
        # }

        self.atom = atom
        assert isinstance(atom, si.Atom)
        if "." in self.atom.tag:
            raise ValueError("The atom 'tag' must not contain a '.'!")

        # We need to check that atom has 4 orbitals, with increasing l
        # We don't care about n or any other stuff, so these could be
        # SphericalOrbital, for that matter
        l = 0
        for orb in self.atom:
            if orb.l != l:
                raise ValueError(
                    f"{self.__class__.__name__} atom argument does not have "
                    f"increasing l quantum number index {l} has l={orb.l}")
            l += 1
        if l != 4:
            raise ValueError(
                f"{self.__class__.__name__} atom argument must have 4 orbitals. "
                f"One for each s-p-d-f shell")

        self.opts = PropertyDict(**opts)

        # Check options passed and define defaults

        self.opts.setdefault("equation", "r")
        if self.opts.equation not in ' rs':
            # ' ' == non-polarized
            # s == polarized
            # r == relativistic
            raise ValueError(
                f"{self.__class__.__name__} failed to initialize; opts{'equation': <v>} has wrong value, should be [ rs]."
            )
        if self.opts.equation == 's':
            raise NotImplementedError(
                f"{self.__class__.__name__} does not implement spin-polarized option (use relativistic)"
            )

        self.opts.setdefault("flavor", "tm2")
        if self.opts.flavor not in ('hsc', 'ker', 'tm2'):
            # hsc == Hamann-Schluter-Chiang
            # ker == Kerker
            # tm2 == Troullier-Martins
            raise ValueError(
                f"{self.__class__.__name__} failed to initialize; opts{'flavor': <v>} has wrong value, should be [hsc|ker|tm2]."
            )

        self.opts.setdefault("logr", 2.)

        # default to true if set
        self.opts.setdefault("cc", "rcore" in self.opts)
        # rcore only used if cc is True
        self.opts.setdefault("rcore", 2.)
        self.opts.setdefault("xc", "pb")

        # Read in the core valence shells for this atom
        # figure out what the default value is.
        # We do this my finding the minimum index of valence shells
        # in the _shell_order list, then we use that as the default number
        # of core-shells occpupied
        # e.g if the minimum valence shell is 2p, it would mean that
        #  _shell_order.index("2p") == 2
        # which has 1s and 2s occupied.
        spdf = 'spdf'
        try:
            core = reduce(min, (_shell_order.index(f"{orb.n}{spdf[orb.l]}")
                                for orb in atom), len(_shell_order))
        except:
            core = -1

        self.opts.setdefault("core", core)
        if self.opts.core == -1:
            raise ValueError(
                f"Default value for {self.atom.symbol} not added, please add core= at instantiation"
            )

        # Store the defined names
        if define is None:
            self.define = []
        elif isinstance(define, str):
            self.define = [define]
        else:
            # must be list-like
            self.define = define

    @classmethod
    def from_input(cls, inp):
        """ Return atom object respecting the input

        Parameters
        ----------
        inp : list or str
           create `AtomInput` from the content of `inp`
        """
        def _get_content(f):
            if f.is_file():
                return open(f, 'r').readlines()
            return None

        if isinstance(inp, (tuple, list)):
            # it is already in correct format
            pass
        elif isinstance(inp, (str, Path)):
            # convert to path
            inp = Path(inp)

            # Check if it is a path or an input
            content = _get_content(inp)
            if content is None:
                content = _get_content(inp / "INP")
            if content is None:
                raise ValueError(
                    f"Could not find any input file in {str(inp)} or {str(inp / 'INP')}"
                )
            inp = content

        else:
            raise ValueError(f"Unknown input format inp={inp}?")

        # Now read lines
        defines = []
        opts = PropertyDict()

        def bypass_comments(inp):
            if inp[0].startswith("#"):
                inp.pop(0)
                bypass_comments(inp)

        def bypass(inp, defines):
            bypass_comments(inp)
            if inp[0].startswith("%define"):
                line = inp.pop(0)
                defines.append(line.split()[1].strip())
                bypass(inp, defines)

        bypass(inp, defines)

        # Now prepare reading
        # First line has to contain the *type* of calculation
        # pg|pe|ae|pt <comment>
        line = inp.pop(0).strip()
        if line.startswith("pg"):
            opts.cc = False
        elif line.startswith("pe"):
            opts.cc = True

        # <flavor> logr?
        line = inp.pop(0).strip().split()
        opts.flavor = line[0]
        if len(line) >= 2:
            opts.logr = float(line[1]) / _Ang2Bohr

        # <element> <xc>' rs'?
        line = inp.pop(0)
        symbol = line.split()[0]
        # now get xc equation
        if len(line) >= 11:
            opts.equation = line[10:10]
        opts.xc = line[:10].split()[1]
        line = line.split()
        if len(line) >= 3:
            opts.libxc = int(line[2])

        # currently not used line
        inp.pop(0)

        # core, valence
        core, valence = inp.pop(0).split()
        opts.core = int(core)
        valence = int(valence)

        orbs = []
        for _ in range(valence):
            n, l, *occ = inp.pop(0).split()
            orb = PropertyDict()
            orb.n = int(n)
            orb.l = int(l)
            # currently we don't distinguish between up/down
            orb.q0 = sum(map(float, occ))
            orbs.append(orb)

        # now we read the line with rc's and core-correction
        rcs = inp.pop(0).split()
        if len(rcs) >= 6:
            # core-correction
            opts.rcore = float(rcs[5]) / _Ang2Bohr

        for orb in orbs:
            orb.R = float(rcs[orb.l]) / _Ang2Bohr

        # Now create orbitals
        orbs = [si.AtomicOrbital(**orb, m=0, zeta=1) for orb in orbs]
        # now re-arrange ensuring we have correct order of l shells
        orbs = sorted(orbs, key=lambda orb: orb.l)
        atom = si.Atom(symbol, orbs)
        return cls(atom, defines, **opts)

    def _write_header(self, f):
        f.write("# This file is generated by sisl pseudo\n")
        # Define all names
        for define in self.define:
            f.write(f"%define {define.upper()}\n")

    def _write_middle(self, f):
        xc = self.opts.xc
        equation = self.opts.equation
        rcore = self.opts.rcore * _Ang2Bohr
        f.write(f"   {self.atom.symbol:2s}   {xc:2s}{equation:1s}")
        if "libxc" in self.opts:
            f.write(f" {self.opts.libxc:8d}")
        f.write(f"\n  {0.0:5.1f}\n")
        # now extract the charges for each orbital
        atom = self.atom
        core = self.opts.core
        valence = len(atom)

        f.write(f"{core:5d}{valence:5d}\n")

        orbs = sorted(atom.orbitals, key=lambda x: x.l)
        Rs = [0.] * 4  # always 4: s, p, d, f
        for orb in orbs:
            # Write the configuration of this orbital
            n = orb.n
            l = orb.l
            # for now this is a single integer
            q0 = orb.q0
            f.write(f"{n:5d}{l:5d}{q0:10.3f}{0.0:10.3f}\n")
            Rs[l] = orb.R * _Ang2Bohr
        f.write(
            f"{Rs[0]:10.7f} {Rs[1]:10.7f} {Rs[2]:10.7f} {Rs[3]:10.7f} {0.0:10.7f} {rcore:10.7f}\n"
        )

    def _get_out(self, path, filename):
        if path is None:
            return Path(filename)
        return Path(path) / Path(filename)

    def ae(self, filename="INP", path=None):
        out = self._get_out(path, filename)
        with open(out, 'w') as f:
            self._write_header(f)
            # Now prepare data
            f.write(f"   ae {self.atom.symbol} ground state calculation\n")
            self._write_middle(f)

    def pg(self, filename="INP", path=None):
        # check whether we need core corrections
        out = self._get_out(path, filename)
        if self.opts.cc:
            # use core corrections
            pg = "pe"
        else:
            # do not use core corrections
            pg = "pg"
        logr = self.opts.logr * _Ang2Bohr
        with open(out, 'w') as f:
            self._write_header(f)
            # Now prepare data
            f.write(
                f"   {pg:2s} {self.atom.symbol} pseudo potential generation\n")
            if logr < 0.:
                f.write(f"        {self.opts.flavor:3s}\n")
            else:
                f.write(f"        {self.opts.flavor:3s}{logr:9.3f}\n")
            self._write_middle(f)

    def plot(self,
             path=None,
             plot=('wavefunction', 'charge', 'log', 'potential'),
             l='spdf',
             show=True):
        """ Plot everything related to this psf file

        Parameters
        ----------
        path : str or pathlib.Path, optional
           from which directory should files be read
        plot : list-like of str, optional
           which data to plot
        l : list-like, optional
           which l-shells to plot (for those that have l-shell decompositions)
        show : bool, optional
           call `matplotlib.pyplot.show()` at the end

        Returns
        -------
        fig : figure for axes
        axs : axes used for plotting
        """
        import matplotlib.pyplot as plt
        if path is None:
            path = Path.cwd()
        else:
            path = Path(path)

        def get_xy(f, yfactors=None):
            """ Return x, y data from file `f` with y being calculated as the factors between the columns """
            nonlocal path
            f = path / f
            if not f.is_file():
                print(f"Could not find file: {str(f)}")
                return None, None

            data = np.loadtxt(f)
            ncol = data.shape[1]

            if yfactors is None:
                yfactors = [0, 1]
            yfactors = np.pad(yfactors, (0, ncol - len(yfactors)),
                              constant_values=0.)
            x = data[:, 0]
            y = (data * yfactors.reshape(1, -1)).sum(1)
            return x, y

        spdfg = 'spdfg'
        l2i = {
            's': 0,
            0: 0,
            'p': 1,
            1: 1,
            'd': 2,
            2: 2,
            'f': 3,
            3: 3,
            'g': 4,
            4: 4,  # never used
        }

        # Get this atoms default calculated binding length
        # We use this one since there are many missing elements
        # in vdw table.
        # And convert to Bohr
        atom_r = self.atom.radius("calc") * _Ang2Bohr

        def plot_wavefunction(ax):
            # somewhat similar to ae.gplot
            ax.set_title("Wavefunction")
            ax.set_xlabel("Radius [Bohr]")

            for shell in l:
                il = l2i[shell]
                orb = self.atom.orbitals[il]

                r, w = get_xy(f"AEWFNR{il}")
                if not r is None:
                    p = ax.plot(r, w, label=f"AE {spdfg[il]}")
                    color = p[0].get_color()
                    ax.axvline(orb.R * _Ang2Bohr, color=color, alpha=0.5)

                r, w = get_xy(f"PSWFNR{il}")
                if not r is None:
                    ax.plot(r, w, '--', label=f"PS {spdfg[il]}")

            ax.set_xlim(0, atom_r * 5)
            ax.autoscale(enable=True, axis='y', tight=True)
            ax.legend()

        def plot_charge(ax):
            ax.set_title("Charge")
            ax.set_xlabel("Radius [Bohr]")
            ax.set_ylabel("(4.pi.r^2) Charge [electrons/Bohr]")

            # Get current core-correction length
            ae_r, ae_cc = get_xy("AECHARGE", [0, 0, 0, 1])
            _, ae_vc = get_xy("AECHARGE", [0, 1, 1, -1])

            if not ae_cc is None:
                p = ax.plot(ae_r, ae_cc, label=f"AE core")
                color = p[0].get_color()
                if self.opts.get("cc", False):
                    ax.axvline(self.opts.rcore * _Ang2Bohr,
                               color=color,
                               alpha=0.5)
                ax.plot(ae_r, ae_vc, '--', label=f"AE valence")

            ps_r, ps_cc = get_xy("PSCHARGE", [0, 0, 0, 1])
            _, ps_vc = get_xy("PSCHARGE", [0, 1, 1])

            if not ps_r is None:
                ax.plot(ps_r, ps_cc, '--', label=f"PS core")
                ax.plot(ps_r, ps_vc, ':', label=f"PS valence")

            # Now determine the overlap between all-electron core-charge
            # and the pseudopotential valence charge
            if np.allclose(ae_r, ps_r):
                # Determine dR
                dr = ae_r[1] - ae_r[0]

                # Integrate number of core-electrons and valence electrons
                core_c = np.trapz(ae_cc, ae_r)
                valence_c = np.trapz(ps_vc, ps_r)
                print(f"Total charge in atom: {core_c + valence_c:.5f}")
                overlap_c = np.trapz(np.minimum(ae_cc, ps_vc), ae_r)
                ax.set_title(
                    f"Charge: int(min(AE_cc, PS_vc)) = {overlap_c:.3f} e")

                # We will try and *guess-stimate* a good position for rc for core-corrections
                # Javier Junquera's document says:
                # r_pc has to be chosen such that the valence charge density is negligeable compared to
                # the core one for r < r_pc.
                # Tests show that it might be located where the core charge density is from 1 to 2 times
                # larger than the valence charge density
                with np.errstate(divide='ignore', invalid='ignore'):
                    core_over_valence = ae_cc / ps_vc

                ax2 = ax.twinx(
                )  # instantiate a second axes that shares the same x-axis
                ax2.plot(ae_r, core_over_valence, 'k', alpha=0.5)

                # Now mark 1, 1.5 and 2 times
                factor_marks = [2., 1.5, 1]
                r_marks = []
                for mark in factor_marks:
                    # last value closest to function
                    idx = (core_over_valence > mark).nonzero()[0][-1]
                    r_marks.append(ae_r[idx])
                ax2.scatter(r_marks, factor_marks, alpha=0.5)
                ax2.set_ylim(0, 3)
                print(f"Core-correction r_pc {factor_marks}: {r_marks} Bohr")

            ax.set_xlim(0, atom_r)
            ax.set_ylim(0)
            ax.legend()

        def plot_log(ax):
            ax.set_title("d-log of wavefunction")
            ax.set_xlabel("Energy [Ry]")
            ax.set_ylabel("Derivative of wavefunction")

            for shell in l:
                il = l2i[shell]

                e, log = get_xy(f"AELOGD{il}")
                emark = np.loadtxt(path / f"AEEV{il}")
                if emark.ndim == 1:
                    emark.shape = (1, -1)
                emark = emark[:, 0]
                if not e is None:
                    p = ax.plot(e, log, label=f"AE {spdfg[il]}")

                    idx_mark = (
                        np.fabs(e.reshape(-1, 1) -
                                emark.reshape(1, -1)).argmin(axis=0))
                    ax.scatter(emark,
                               log[idx_mark],
                               color=p[0].get_color(),
                               alpha=0.5)

                # And now PS
                e, log = get_xy(f"PSLOGD{il}")
                emark = np.loadtxt(path / f"PSEV{il}")
                if emark.ndim == 1:
                    emark.shape = (1, -1)
                emark = emark[:, 0]
                if not e is None:
                    p = ax.plot(e, log, label=f"PS {spdfg[il]}")

                    idx_mark = (
                        np.fabs(e.reshape(-1, 1) -
                                emark.reshape(1, -1)).argmin(axis=0))
                    ax.scatter(emark,
                               log[idx_mark],
                               color=p[0].get_color(),
                               alpha=0.5)

            ax.legend()

        def plot_potential(ax):
            ax.set_title("Pseudopotential")
            ax.set_xlabel("Radius [Bohr]")
            ax.set_ylabel("Potential [Ry]")

            for shell in l:
                il = l2i[shell]
                orb = self.atom.orbitals[il]

                r, V = get_xy(f"PSPOTR{il}")
                if not r is None:
                    p = ax.plot(r, V, label=f"PS {spdfg[il]}")
                    color = p[0].get_color()
                    ax.axvline(orb.R * _Ang2Bohr, color=color, alpha=0.5)

            ax.set_xlim(0, atom_r * 3)
            ax.legend()

        nrows = len(l) // 2
        ncols = len(l) // nrows
        if nrows * ncols < len(l):
            ncols += 1
        fig, axs = plt.subplots(nrows, ncols, figsize=(11, 10))

        def next_rc(ir, ic, nrows, ncols):
            ic = ic + 1
            if ic == ncols:
                ic = 0
                ir = ir + 1
            return ir, ic

        ir, ic = 0, 0
        for this_plot in map(lambda x: x.lower(), plot):
            if this_plot == "wavefunction":
                plot_wavefunction(axs[ir][ic])
            elif this_plot == "log":
                plot_log(axs[ir][ic])
            elif this_plot == "charge":
                plot_charge(axs[ir][ic])
            elif this_plot == "potential":
                plot_potential(axs[ir][ic])

            ir, ic = next_rc(ir, ic, nrows, ncols)

        if show:
            plt.show()
        return fig, axs
예제 #4
0
파일: _atom.py 프로젝트: tfrederiksen/sisl
    def from_input(cls, inp):
        """ Return atom object respecting the input

        Parameters
        ----------
        inp : list or str
           create `AtomInput` from the content of `inp`
        """
        def _get_content(f):
            if f.is_file():
                return open(f, 'r').readlines()
            return None

        if isinstance(inp, (tuple, list)):
            # it is already in correct format
            pass
        elif isinstance(inp, (str, Path)):
            # convert to path
            inp = Path(inp)

            # Check if it is a path or an input
            content = _get_content(inp)
            if content is None:
                content = _get_content(inp / "INP")
            if content is None:
                raise ValueError(
                    f"Could not find any input file in {str(inp)} or {str(inp / 'INP')}"
                )
            inp = content

        else:
            raise ValueError(f"Unknown input format inp={inp}?")

        # Now read lines
        defines = []
        opts = PropertyDict()

        def bypass_comments(inp):
            if inp[0].startswith("#"):
                inp.pop(0)
                bypass_comments(inp)

        def bypass(inp, defines):
            bypass_comments(inp)
            if inp[0].startswith("%define"):
                line = inp.pop(0)
                defines.append(line.split()[1].strip())
                bypass(inp, defines)

        bypass(inp, defines)

        # Now prepare reading
        # First line has to contain the *type* of calculation
        # pg|pe|ae|pt <comment>
        line = inp.pop(0).strip()
        if line.startswith("pg"):
            opts.cc = False
        elif line.startswith("pe"):
            opts.cc = True

        # <flavor> logr?
        line = inp.pop(0).strip().split()
        opts.flavor = line[0]
        if len(line) >= 2:
            opts.logr = float(line[1]) / _Ang2Bohr

        # <element> <xc>' rs'?
        line = inp.pop(0)
        symbol = line.split()[0]
        # now get xc equation
        if len(line) >= 11:
            opts.equation = line[10:10]
        opts.xc = line[:10].split()[1]
        line = line.split()
        if len(line) >= 3:
            opts.libxc = int(line[2])

        # currently not used line
        inp.pop(0)

        # core, valence
        core, valence = inp.pop(0).split()
        opts.core = int(core)
        valence = int(valence)

        orbs = []
        for _ in range(valence):
            n, l, *occ = inp.pop(0).split()
            orb = PropertyDict()
            orb.n = int(n)
            orb.l = int(l)
            # currently we don't distinguish between up/down
            orb.q0 = sum(map(float, occ))
            orbs.append(orb)

        # now we read the line with rc's and core-correction
        rcs = inp.pop(0).split()
        if len(rcs) >= 6:
            # core-correction
            opts.rcore = float(rcs[5]) / _Ang2Bohr

        for orb in orbs:
            orb.R = float(rcs[orb.l]) / _Ang2Bohr

        # Now create orbitals
        orbs = [si.AtomicOrbital(**orb, m=0, zeta=1) for orb in orbs]
        # now re-arrange ensuring we have correct order of l shells
        orbs = sorted(orbs, key=lambda orb: orb.l)
        atom = si.Atom(symbol, orbs)
        return cls(atom, defines, **opts)
예제 #5
0
파일: _atom.py 프로젝트: tfrederiksen/sisl
    def __init__(self,
                 atom,
                 define=('NEW_CC', 'FREE_FORMAT_RC_INPUT', 'NO_PS_CUTOFFS'),
                 **opts):
        # opts = {
        #   "flavor": "tm2",
        #   "xc": "pb",
        #  optionally libxc
        #   "equation": "r",
        #   "logr": 2.
        #   "cc": False,
        #   "rcore": 2.
        # }

        self.atom = atom
        assert isinstance(atom, si.Atom)
        if "." in self.atom.tag:
            raise ValueError("The atom 'tag' must not contain a '.'!")

        # We need to check that atom has 4 orbitals, with increasing l
        # We don't care about n or any other stuff, so these could be
        # SphericalOrbital, for that matter
        l = 0
        for orb in self.atom:
            if orb.l != l:
                raise ValueError(
                    f"{self.__class__.__name__} atom argument does not have "
                    f"increasing l quantum number index {l} has l={orb.l}")
            l += 1
        if l != 4:
            raise ValueError(
                f"{self.__class__.__name__} atom argument must have 4 orbitals. "
                f"One for each s-p-d-f shell")

        self.opts = PropertyDict(**opts)

        # Check options passed and define defaults

        self.opts.setdefault("equation", "r")
        if self.opts.equation not in ' rs':
            # ' ' == non-polarized
            # s == polarized
            # r == relativistic
            raise ValueError(
                f"{self.__class__.__name__} failed to initialize; opts{'equation': <v>} has wrong value, should be [ rs]."
            )
        if self.opts.equation == 's':
            raise NotImplementedError(
                f"{self.__class__.__name__} does not implement spin-polarized option (use relativistic)"
            )

        self.opts.setdefault("flavor", "tm2")
        if self.opts.flavor not in ('hsc', 'ker', 'tm2'):
            # hsc == Hamann-Schluter-Chiang
            # ker == Kerker
            # tm2 == Troullier-Martins
            raise ValueError(
                f"{self.__class__.__name__} failed to initialize; opts{'flavor': <v>} has wrong value, should be [hsc|ker|tm2]."
            )

        self.opts.setdefault("logr", 2.)

        # default to true if set
        self.opts.setdefault("cc", "rcore" in self.opts)
        # rcore only used if cc is True
        self.opts.setdefault("rcore", 2.)
        self.opts.setdefault("xc", "pb")

        # Read in the core valence shells for this atom
        # figure out what the default value is.
        # We do this my finding the minimum index of valence shells
        # in the _shell_order list, then we use that as the default number
        # of core-shells occpupied
        # e.g if the minimum valence shell is 2p, it would mean that
        #  _shell_order.index("2p") == 2
        # which has 1s and 2s occupied.
        spdf = 'spdf'
        try:
            core = reduce(min, (_shell_order.index(f"{orb.n}{spdf[orb.l]}")
                                for orb in atom), len(_shell_order))
        except:
            core = -1

        self.opts.setdefault("core", core)
        if self.opts.core == -1:
            raise ValueError(
                f"Default value for {self.atom.symbol} not added, please add core= at instantiation"
            )

        # Store the defined names
        if define is None:
            self.define = []
        elif isinstance(define, str):
            self.define = [define]
        else:
            # must be list-like
            self.define = define
예제 #6
0
파일: out.py 프로젝트: tfrederiksen/sisl
    def read_charge(self,
                    name,
                    iscf=Opt.ANY,
                    imd=Opt.ANY,
                    key_scf="scf",
                    as_dataframe=False):
        r"""Read charges calculated in SCF loop or MD loop (or both)

        Siesta enables many different modes of writing out charges.

        NOTE: currently Mulliken charges are not implemented.

        The below table shows a list of different cases that
        may be encountered, the letters are referred to in the
        return section to indicate what is returned.

        +-----------+-----+-----+--------+-------+------------------+
        | Case      | *A* | *B* | *C*    | *D*   | *E*              |
        +-----------+-----+-----+--------+-------+------------------+
        | Charge    | MD  | SCF | MD+SCF | Final | Orbital resolved |
        +-----------+-----+-----+--------+-------+------------------+
        | Voronoi   | +   | +   | +      | +     | -                |
        +-----------+-----+-----+--------+-------+------------------+
        | Hirshfeld | +   | +   | +      | +     | -                |
        +-----------+-----+-----+--------+-------+------------------+
        | Mulliken  | +   | +   | +      | +     | +                |
        +-----------+-----+-----+--------+-------+------------------+

        Notes
        -----
        Errors will be raised if one requests information not present. I.e.
        passing an integer or `Opt.ALL` for `iscf` will raise an error if
        the SCF charges are not present. For `Opt.ANY` it will return
        the most information, effectively SCF will be returned if present.

        Currently Mulliken is not implemented, any help in reading this would be
        very welcome.

        Parameters
        ----------
        name: {"voronoi", "hirshfeld"}
            the name of the charges that you want to read
        iscf: int or Opt, optional
            index (0-based) of the scf iteration you want the charges for.
            If the enum specifier `Opt.ANY` or `Opt.ALL` are used, then
            the returned quantities depend on what is present.
            If ``None/Opt.NONE`` it will not return any SCF charges.
            If both `imd` and `iscf` are ``None`` then only the final charges will be returned.
        imd: int or Opt, optional
            index (0-based) of the md step you want the charges for.
            If the enum specifier `Opt.ANY` or `Opt.ALL` are used, then
            the returned quantities depend on what is present.
            If ``None/Opt.NONE`` it will not return any MD charges.
            If both `imd` and `iscf` are ``None`` then only the final charges will be returned.
        key_scf : str, optional
            the key lookup for the scf iterations (a ":" will automatically be appended)
        as_dataframe: boolean, optional
            whether charges should be returned as a pandas dataframe.

        Returns
        -------
        numpy.ndarray
            if a specific MD+SCF index is requested (or special cases where output is
            not complete)
        list of numpy.ndarray
            if one both `iscf` or `imd` is different from ``None/Opt.NONE``.
        pandas.DataFrame
            if `as_dataframe` is requested. The dataframe will have multi-indices if multiple
            SCF or MD steps are requested.
        """
        if not hasattr(self, 'fh'):
            with self:
                return read_charge(self, name, iscf, imd, key_scf,
                                   as_dataframe)
        namel = name.lower()
        if as_dataframe:
            import pandas as pd

            def _empty_charge():
                # build a fake dataframe with no indices
                return pd.DataFrame(index=pd.Index([],
                                                   name="atom",
                                                   dtype=np.int32),
                                    dtype=np.float32)
        else:
            pd = None

            def _empty_charge():
                # return for single value with nan values
                return _a.arrayf([[None]])

        # define helper function for reading voronoi+hirshfeld charges
        def _voronoi_hirshfeld_charges():
            """ Read output from Voronoi/Hirshfeld charges """
            nonlocal pd

            # Expecting something like this:
            # Voronoi Atomic Populations:
            # Atom #     dQatom  Atom pop         S        Sx        Sy        Sz  Species
            #      1   -0.02936   4.02936   0.00000  -0.00000   0.00000   0.00000  C

            # Define the function that parses the charges
            def _parse_charge(line):
                atom_idx, *vals, symbol = line.split()
                # assert that this is a proper line
                # this should catch cases where the following line of charge output
                # is still parseable
                atom_idx = int(atom_idx)
                return list(map(float, vals))

            # first line is the header
            header = (
                self.readline().replace("dQatom", "dq")  # dQatom in master
                .replace(" Qatom", " dq")  # Qatom in 4.1
                .replace("Atom pop", "e")  # not found in 4.1
                .split())[2:-1]

            # We have found the header, prepare a list to read the charges
            atom_charges = []
            line = ' '
            while line != "":
                try:
                    line = self.readline()
                    charge_vals = _parse_charge(line)
                    atom_charges.append(charge_vals)
                except:
                    # We already have the charge values and we reached a line that can't be parsed,
                    # this means we have reached the end.
                    break
            if pd is None:
                # not as_dataframe
                return _a.arrayf(atom_charges)

            # determine how many columns we have
            # this will remove atom indices and species, so only inside
            ncols = len(atom_charges[0])
            assert ncols == len(header)

            # the precision is limited, so no need for double precision
            return pd.DataFrame(atom_charges,
                                columns=header,
                                dtype=np.float32,
                                index=pd.RangeIndex(stop=len(atom_charges),
                                                    name="atom"))

        # define helper function for reading voronoi+hirshfeld charges
        def _mulliken_charges():
            """ Read output from Mulliken charges """
            raise NotImplementedError(
                "Mulliken charges are not implemented currently")

        # Check that a known charge has been requested
        if namel == "voronoi":
            _r_charge = _voronoi_hirshfeld_charges
            charge_keys = [
                "Voronoi Atomic Populations", "Voronoi Net Atomic Populations"
            ]
        elif namel == "hirshfeld":
            _r_charge = _voronoi_hirshfeld_charges
            charge_keys = [
                "Hirshfeld Atomic Populations",
                "Hirshfeld Net Atomic Populations"
            ]
        elif namel == "mulliken":
            _r_charge = _mulliken_charges
            charge_keys = ["mulliken: Atomic and Orbital Populations"]
        else:
            raise ValueError(
                f"{self.__class__.__name__}.read_charge name argument should be one of {known_charges}, got {name}?"
            )

        # Ensure the key_scf matches exactly (prepend a space)
        key_scf = f" {key_scf.strip()}:"

        # Reading charges may be quite time consuming for large MD simulations.

        # to see if we finished a MD read, we check for these keys
        search_keys = [
            # two keys can signal ending SCF
            "SCF Convergence",
            "SCF_NOT_CONV",
            "siesta: Final energy",
            key_scf,
            *charge_keys
        ]
        # adjust the below while loop to take into account any additional
        # segments of search_keys
        IDX_SCF_END = [0, 1]
        IDX_FINAL = [2]
        IDX_SCF = [3]
        # the rest are charge keys
        IDX_CHARGE = list(
            range(len(search_keys) - len(charge_keys), len(search_keys)))

        # state to figure out where we are
        state = PropertyDict()
        state.INITIAL = 0
        state.MD = 1
        state.SCF = 2
        state.CHARGE = 3
        state.FINAL = 4

        # a list of scf_charge
        md_charge = []
        md_scf_charge = []
        scf_charge = []
        final_charge = None

        # signal that any first reads are INITIAL charges
        current_state = state.INITIAL
        charge = _empty_charge()
        FOUND_SCF = False
        FOUND_MD = False
        FOUND_FINAL = False

        # TODO whalrus
        ret = self.step_to(search_keys,
                           case=True,
                           ret_index=True,
                           reread=False)
        while ret[0]:
            if ret[2] in IDX_SCF_END:
                # we finished all SCF iterations
                current_state = state.MD
                md_scf_charge.append(scf_charge)
                scf_charge = []

            elif ret[2] in IDX_SCF:
                current_state = state.SCF
                # collect scf-charges (possibly none)
                scf_charge.append(charge)

            elif ret[2] in IDX_FINAL:
                current_state = state.FINAL
                # don't do anything, this is the final charge construct
                # regardless of where it comes from.

            elif ret[2] in IDX_CHARGE:
                FOUND_CHARGE = True
                # also read charge
                charge = _r_charge()

                if state.INITIAL == current_state or state.CHARGE == current_state:
                    # this signals scf charges
                    FOUND_SCF = True
                    # There *could* be 2 steps if we are mixing H,
                    # this is because it first does
                    # compute H -> compute DM -> compute H
                    # in the first iteration, subsequently we only do
                    # compute compute DM -> compute H
                    # once we hit ret[2] in IDX_SCF we will append
                    scf_charge = []

                elif state.MD == current_state:
                    FOUND_MD = True
                    # we just finished an SCF cycle.
                    # So any output between SCF ending and
                    # a new one beginning *must* be that geometries
                    # charge

                    # Here `charge` may be NONE signalling
                    # we don't have charge in MD steps.
                    md_charge.append(charge)

                    # reset charge
                    charge = _empty_charge()

                elif state.SCF == current_state:
                    FOUND_SCF = True

                elif state.FINAL == current_state:
                    FOUND_FINAL = True
                    # a special state writing out the charges after everything
                    final_charge = charge
                    charge = _empty_charge()
                    scf_charge = []
                    # we should be done and no other charge reads should be found!
                    # should we just break?

                current_state = state.CHARGE

            # step to next entry
            ret = self.step_to(search_keys,
                               case=True,
                               ret_index=True,
                               reread=False)

        if not any((FOUND_SCF, FOUND_MD, FOUND_FINAL)):
            raise SileError(
                f"{str(self)} does not contain any charges ({name})")

        # if the scf-charges are not stored, it means that the MD step finalization
        # has not been read. So correct
        if len(scf_charge) > 0:
            assert False, "this test shouldn't reach here"
            # we must not have read through the entire MD step
            # so this has to be a running simulation
            if charge is not None:
                scf_charge.append(charge)
                charge = _empty_charge()
            md_scf_charge.append(scf_charge)

        # otherwise there is some *parsing* error, so for now we use assert
        assert len(scf_charge) == 0

        if as_dataframe:
            # convert data to proper data structures
            # regardless of user requests. This is an overhead... But probably not that big of a problem.
            if FOUND_SCF:
                md_scf_charge = pd.concat([
                    pd.concat(iscf,
                              keys=pd.RangeIndex(1, len(iscf) + 1,
                                                 name="iscf"))
                    for iscf in md_scf_charge
                ],
                                          keys=pd.RangeIndex(
                                              1,
                                              len(md_scf_charge) + 1,
                                              name="imd"))
            if FOUND_MD:
                md_charge = pd.concat(md_charge,
                                      keys=pd.RangeIndex(1,
                                                         len(md_charge) + 1,
                                                         name="imd"))
        else:
            if FOUND_SCF:
                nan_array = _a.emptyf(md_scf_charge[0][0].shape)
                nan_array.fill(np.nan)

                def get_md_scf_charge(scf_charge, iscf):
                    try:
                        return scf_charge[iscf]
                    except:
                        return nan_array

            if FOUND_MD:
                md_charge = np.stack(md_charge)

        # option parsing is a bit *difficult* with flag enums
        # So first figure out what is there, and handle this based
        # on arguments
        def _p(flag, found):
            """ Helper routine to do the following:

            Returns
            -------
            is_opt : bool
                whether the flag is an `Opt`
            flag :
                corrected flag
            """
            if isinstance(flag, Opt):
                # correct flag depending on what `found` is
                # If the values have been found we
                # change flag to None only if flag == NONE
                # If the case has not been found, we
                # change flag to None if ANY or NONE is in flags

                if found:
                    # flag is only NONE, then pass none
                    if not (Opt.NONE ^ flag):
                        flag = None
                else:  # not found
                    # we convert flag to none
                    # if ANY or NONE in flag
                    if (Opt.NONE | Opt.ANY) & flag:
                        flag = None

            return isinstance(flag, Opt), flag

        opt_imd, imd = _p(imd, FOUND_MD)
        opt_iscf, iscf = _p(iscf, FOUND_SCF)

        if not (FOUND_SCF or FOUND_MD):
            # none of these are found
            # we request that user does not request any input
            if (opt_iscf or (not iscf is None)) or \
               (opt_imd or (not imd is None)):
                raise SileError(f"{str(self)} does not contain MD/SCF charges")

        elif not FOUND_SCF:
            if opt_iscf or (not iscf is None):
                raise SileError(f"{str(self)} does not contain SCF charges")

        elif not FOUND_MD:
            if opt_imd or (not imd is None):
                raise SileError(f"{str(self)} does not contain MD charges")

        # if either are options they may hold
        if opt_imd and opt_iscf:
            if FOUND_SCF:
                return md_scf_charge
            elif FOUND_MD:
                return md_charge
            elif FOUND_FINAL:
                # I think this will never be reached
                # If neither are found they will be converted to
                # None
                return final_charge

            raise SileError(
                f"{str(self)} unknown argument for 'imd' and 'iscf'")

        elif opt_imd:
            # flag requested imd
            if not (imd & (Opt.ANY | Opt.ALL)):
                # wrong flag
                raise SileError(f"{str(self)} unknown argument for 'imd'")

            if FOUND_SCF and iscf is not None:
                # this should be handled, i.e. the scf should be taken out
                if as_dataframe:
                    return md_scf_charge.groupby(level=[0, 2]).nth(iscf)
                return np.stack(
                    tuple(get_md_scf_charge(x, iscf) for x in md_scf_charge))

            elif FOUND_MD and iscf is None:
                return md_charge
            raise SileError(
                f"{str(self)} unknown argument for 'imd' and 'iscf', could not find SCF charges"
            )

        elif opt_iscf:
            # flag requested imd
            if not (iscf & (Opt.ANY | Opt.ALL)):
                # wrong flag
                raise SileError(f"{str(self)} unknown argument for 'iscf'")
            if imd is None:
                # correct imd
                imd = -1
            if as_dataframe:
                md_scf_charge = md_scf_charge.groupby(level=0)
                group = list(md_scf_charge.groups.keys())[imd]
                return md_scf_charge.get_group(group).droplevel(0)
            return np.stack(md_scf_charge[imd])

        elif imd is None and iscf is None:
            if FOUND_FINAL:
                return final_charge
            raise SileError(f"{str(self)} does not contain final charges")

        elif imd is None:
            # iscf is not None, so pass through as though explicitly passed
            imd = -1

        elif iscf is None:
            # we return the last MD step and the requested scf iteration
            if as_dataframe:
                return md_charge.groupby(level=1).nth(imd)
            return md_charge[imd]

        if as_dataframe:
            # first select imd
            md_scf_charge = md_scf_charge.groupby(level=0)
            group = list(md_scf_charge.groups.keys())[imd]
            md_scf_charge = md_scf_charge.get_group(group).droplevel(0)
            return md_scf_charge.groupby(level=1).nth(iscf)
        return md_scf_charge[imd][iscf]
예제 #7
0
파일: out.py 프로젝트: tfrederiksen/sisl
    def read_energy(self):
        """ Reads the final energy distribution

        Currently the energies translated are:

        ``band``
             band structure energy
        ``kinetic``
             electronic kinetic energy
        ``hartree``
             electronic electrostatic Hartree energy
        ``dftu``
             DFT+U energy
        ``spin_orbit``
             spin-orbit energy
        ``extE``
             external field energy
        ``xc``
             exchange-correlation energy
        ``exchange``
             exchange energy
        ``correlation``
             correlation energy
        ``bulkV``
             bulk-bias correction energy
        ``total``
             total energy
        ``negf``
             NEGF energy
        ``fermi``
             Fermi energy
        ``ion.electron``
             ion-electron interaction energy
        ``ion.ion``
             ion-ion interaction energy
        ``ion.kinetic``
             kinetic ion energy


        Any unrecognized key gets added *as is*.

        Examples
        --------
        >>> energies = sisl.get_sile("RUN.out").read_energy()
        >>> ion_energies = energies.ion
        >>> ion_energies.ion # ion-ion interaction energy
        >>> ion_energies.kinetic # ion kinetic energy
        >>> energies.fermi # fermi energy

        Returns
        -------
        PropertyDict : dictionary like lookup table ionic energies are stored in a nested `PropertyDict` at the key ``ion`` (all energies in eV)
        """
        found = self.step_to("siesta: Final energy", reread=False)[0]
        out = PropertyDict()
        out.ion = PropertyDict()
        if not found:
            return out
        itt = iter(self)

        # Read data
        line = next(itt)
        name_conv = {
            "Band Struct.": "band",
            "Kinetic": "kinetic",
            "Hartree": "hartree",
            "Edftu": "dftu",
            "Eldau": "dftu",
            "Eso": "spin_orbit",
            "Ext. field": "extE",
            "Exch.-corr.": "xc",
            "Exch.": "exchange",
            "Corr.": "correlation",
            "Ekinion": "ion.kinetic",
            "Ion-electron": "ion.electron",
            "Ion-ion": "ion.ion",
            "Bulk bias": "bulkV",
            "Total": "total",
            "Fermi": "fermi",
            "Enegf": "negf",
        }
        while len(line.strip()) > 0:
            key, val = line.split("=")
            key = key.split(":")[1].strip()
            key = name_conv.get(key, key)
            if key.startswith("ion."):
                # sub-nest
                out.ion[key[4:]] = float(val)
            else:
                out[key] = float(val)
            line = next(itt)

        return out
예제 #8
0
파일: basis.py 프로젝트: juijan/sisl
    def ArgumentParser(self, p=None, *args, **kwargs):
        """ Returns the arguments that is available for this Sile """
        #limit_args = kwargs.get('limit_arguments', True)
        short = kwargs.get('short', False)

        def opts(*args):
            if short:
                return args
            return [args[0]]

        # We limit the import to occur here
        import argparse

        Bohr2Ang = unit_convert('Bohr', 'Ang')
        Ry2eV = unit_convert('Bohr', 'Ang')

        # The first thing we do is adding the geometry to the NameSpace of the
        # parser.
        # This will enable custom actions to interact with the geometry in a
        # straight forward manner.
        # convert netcdf file to a dictionary
        ion_nc = PropertyDict()
        ion_nc.n = self._variable('orbnl_n')[:]
        ion_nc.l = self._variable('orbnl_l')[:]
        ion_nc.zeta = self._variable('orbnl_z')[:]
        ion_nc.pol = self._variable('orbnl_ispol')[:]
        ion_nc.orbital = self._variable('orb')[:]

        # this gets converted later
        delta = self._variable('delta')[:]
        r = aranged(ion_nc.orbital.shape[1]).reshape(1, -1) * delta.reshape(-1, 1)
        ion_nc.orbital *= r ** ion_nc.l.reshape(-1, 1) / Bohr2Ang * (3./2.)
        ion_nc.r = r * Bohr2Ang
        ion_nc.kb = PropertyDict()
        ion_nc.kb.n = self._variable('pjnl_n')[:]
        ion_nc.kb.l = self._variable('pjnl_l')[:]
        ion_nc.kb.e = self._variable('pjnl_ekb')[:] * Ry2eV
        ion_nc.kb.proj = self._variable('proj')[:]
        delta = self._variable('kbdelta')[:]
        r = aranged(ion_nc.kb.proj.shape[1]).reshape(1, -1) * delta.reshape(-1, 1)
        ion_nc.kb.proj *= r ** ion_nc.kb.l.reshape(-1, 1) / Bohr2Ang * (3./2.)
        ion_nc.kb.r = r * Bohr2Ang

        vna = self._variable('vna')
        r = aranged(vna[:].size) * vna.Vna_delta
        ion_nc.vna = PropertyDict()
        ion_nc.vna.v = vna[:] * Ry2eV * r / Bohr2Ang ** 3
        ion_nc.vna.r = r * Bohr2Ang

        # this is charge (not 1/sqrt(charge))
        chlocal = self._variable('chlocal')
        r = aranged(chlocal[:].size) * chlocal.Chlocal_delta
        ion_nc.chlocal = PropertyDict()
        ion_nc.chlocal.v = chlocal[:] * r / Bohr2Ang ** 3
        ion_nc.chlocal.r = r * Bohr2Ang

        vlocal = self._variable('reduced_vlocal')
        r = aranged(vlocal[:].size) * vlocal.Reduced_vlocal_delta
        ion_nc.vlocal = PropertyDict()
        ion_nc.vlocal.v = vlocal[:] * r / Bohr2Ang ** 3
        ion_nc.vlocal.r = r * Bohr2Ang

        if "core" in self.variables:
            # this is charge (not 1/sqrt(charge))
            core = self._variable('core')
            r = aranged(core[:].size) * core.Core_delta
            ion_nc.core = PropertyDict()
            ion_nc.core.v = core[:] * r / Bohr2Ang ** 3
            ion_nc.core.r = r * Bohr2Ang

        d = {
            "_data": ion_nc,
            "_kb_proj": False,
            "_l": True,
            "_n": True,
        }
        namespace = default_namespace(**d)

        # l-quantum number
        class lRange(argparse.Action):

            def __call__(self, parser, ns, value, option_string=None):
                value = (value
                         .replace("s", 0)
                         .replace("p", 1)
                         .replace("d", 2)
                         .replace("f", 3)
                         .replace("g", 4)
                )
                ns._l = strmap(int, value)[0]
        p.add_argument('-l',
                       action=lRange,
                       help='Denote the sub-section of l-shells that are plotted: "s,f"')

        # n quantum number
        class nRange(argparse.Action):

            def __call__(self, parser, ns, value, option_string=None):
                ns._n = strmap(int, value)[0]
        p.add_argument('-n',
                       action=nRange,
                       help='Denote the sub-section of n quantum numbers that are plotted: "2-4,6"')

        class Plot(argparse.Action):

            def __call__(self, parser, ns, value, option_string=None):
                import matplotlib.pyplot as plt

                # Retrieve values
                data = ns._data

                # We have these plots:
                #  - orbitals
                #  - projectors
                #  - chlocal
                #  - vna
                #  - vlocal
                #  - core (optional)

                # We'll plot them like this:
                #  orbitals | projectors
                #  vna + vlocal | chlocal + core
                #
                # Determine different n, l
                fig, axs = plt.subplots(2, 2)

                # Now plot different orbitals
                for n, l, zeta, pol, r, orb in zip(data.n, data.l, data.zeta,
                                                   data.pol, data.r, data.orbital):
                    if pol == 1:
                        pol = 'P'
                    else:
                        pol = ''
                    axs[0][0].plot(r, orb, label=f"n{n}l{l}Z{zeta}{pol}")
                axs[0][0].set_title("Orbitals")
                axs[0][0].set_xlabel("Distance [Ang]")
                axs[0][0].set_ylabel("Value [a.u.]")
                axs[0][0].legend()

                # plot projectors
                for n, l, e, r, proj in zip(
                        data.kb.n, data.kb.l, data.kb.e, data.kb.r, data.kb.proj):
                    axs[0][1].plot(r, proj, label=f"n{n}l{l} e={e:.5f}")
                axs[0][1].set_title("KB projectors")
                axs[0][1].set_xlabel("Distance [Ang]")
                axs[0][1].set_ylabel("Value [a.u.]")
                axs[0][1].legend()

                axs[1][0].plot(data.vna.r, data.vna.v, label='Vna')
                axs[1][0].plot(data.vlocal.r, data.vlocal.v, label='Vlocal')
                axs[1][0].set_title("Potentials")
                axs[1][0].set_xlabel("Distance [Ang]")
                axs[1][0].set_ylabel("Potential [eV]")
                axs[1][0].legend()

                axs[1][1].plot(data.chlocal.r, data.chlocal.v, label='Chlocal')
                if "core" in data:
                    axs[1][1].plot(data.core.r, data.core.v, label='core')
                axs[1][1].set_title("Charge")
                axs[1][1].set_xlabel("Distance [Ang]")
                axs[1][1].set_ylabel("Charge [Ang^3]")
                axs[1][1].legend()

                if value is None:
                    plt.show()
                else:
                    plt.savefig(value)
        p.add_argument(*opts('--plot', '-p'), action=Plot, nargs='?', metavar='FILE',
                       help='Plot the content basis set file, possibly saving plot to a file.')

        return p, namespace
예제 #9
0
    def candidates(self, delta=1e-2, target=None, sort="max"):
        """ Compare samples and find candidates within a delta-metric of `delta`

        Candidiates are ordered around the basis-set sizes.
        This means that *zeta* variables are the only ones used for figuring out
        the candidates.

        Parameters
        ----------
        delta : float, optional
           only consider sampled metrics that lie within ``target +- delta``
        target : float, optional
           target metric value to search around. This may be useful in situations
           where all basis sets are very large and that accuracy isn't really needed.
           Defaults to the minimum metric.
        sort : {max, l1, l2}, callable
           How to sort the basis set ranges. If a callable it should return the
           indices that pivots the data to sorted candidates.
           The callable should accept two arrays, ``(x, y)`` and all variables will
           be passed (not only basis-set ranges).
        """
        # Retrieve all data points within the minim
        x = np.array(self.data.x)
        y = np.array(self.data.y)
        if target is None:
            idx_target = np.argmin(y)
        else:
            idx_target = np.argmin(np.fabs(y - target))
        xtarget = x[idx_target]
        ytarget = y[idx_target]

        # Now find all valid samples
        valid = np.logical_and(ytarget - delta <= y,
                               y <= ytarget + delta).nonzero()[0]

        # Reduce to candidate points
        x_valid = x[valid]
        y_valid = y[valid]

        # Figure out which variables are *basis ranges*
        idx_R = []
        for idx, v in enumerate(self.variables):
            # I think this should be enough for a zeta value
            if ".z" in v.name:
                idx_R.append(idx)

        if len(idx_R) > 0:
            # only use these indices to find minimum candidates
            if isinstance(sort, str):
                if sort == "max":
                    idx_increasing = np.argsort(x_valid[:, idx_R].max(axis=1))
                elif sort == "l1":
                    idx_increasing = np.argsort(x_valid[:, idx_R].sum(axis=1))
                elif sort == "l2":
                    # no need for sqrt (does nothing for sort)
                    idx_increasing = np.argsort(
                        (x_valid[:, idx_R]**2).sum(axis=1))
                else:
                    raise ValueError(
                        f"{self.__class__.__name__}.candidates got an unknown value for 'sort={sort}', must be one of [max,l1,l2]."
                    )
            else:
                # it really has to be callable ;)
                idx_increasing = sort(x_valid, y_valid)

            x_valid = x_valid[idx_increasing]
            y_valid = y_valid[idx_increasing]
        elif callable(sort):
            idx_increasing = sort(x_valid, y_valid)
            x_valid = x_valid[idx_increasing]
            y_valid = y_valid[idx_increasing]

        # Return the candidates
        candidates = PropertyDict()
        candidates.x = x_valid
        candidates.y = y_valid
        candidates.x_target = xtarget
        candidates.y_target = ytarget

        return candidates
예제 #10
0
파일: __init__.py 프로젝트: juijan/sisl
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
""" Default graphene models """
from sisl.utils import PropertyDict

# Here we import the specific details that are exposed
from ._hamiltonian import *

__all__ = ['graphene']

# Define the graphene model
graphene = PropertyDict()
graphene.hamiltonian = GrapheneHamiltonian()
graphene.H = graphene.hamiltonian