Ejemplo n.º 1
0
def hessian_write(wfn: core.Wavefunction):
    if core.get_option('FINDIF', 'HESSIAN_WRITE'):
        filename = core.get_writer_file_prefix(wfn.molecule().name()) + ".hess"
        with open(filename, 'wb') as handle:
            qcdb.hessparse.to_string(np.asarray(wfn.hessian()),
                                     handle,
                                     dtype='psi4')
Ejemplo n.º 2
0
def _core_wavefunction_get_scratch_filename(self, filenumber):
    """ Given a wavefunction and a scratch file number, canonicalizes the name
        so that files can be consistently written and read """
    fname = os.path.split(
        os.path.abspath(core.get_writer_file_prefix(
            self.molecule().name())))[1]
    psi_scratch = core.IOManager.shared_object().get_default_path()
    return os.path.join(psi_scratch, fname + '.' + str(filenumber))
Ejemplo n.º 3
0
def gradient_write(wfn: core.Wavefunction):
    if core.get_option('FINDIF', 'GRADIENT_WRITE'):
        filename = core.get_writer_file_prefix(wfn.molecule().name()) + ".grad"
        qcdb.gradparse.to_string(np.asarray(wfn.gradient()),
                                 filename,
                                 dtype='GRD',
                                 mol=wfn.molecule(),
                                 energy=wfn.energy())
Ejemplo n.º 4
0
    def _init_addghost_C(self, oldcalc, calc):
        # print('Adding ghost %s->%s' % (oldcalc, calc))

        old_filename = self._fmt_mo_fn(oldcalc)

        wfn = core.Wavefunction.from_file(old_filename)

        Ca_occ = wfn.Ca_subset('SO', 'OCC')
        Cb_occ = wfn.Cb_subset('SO', 'OCC')

        m1_nso = self.wfn_cache[('m1', 'm', oldcalc.Z)].nso()
        m2_nso = self.wfn_cache[('m2', 'm', oldcalc.Z)].nso()
        m1_nalpha = self.wfn_cache[('m1', 'm', oldcalc.Z)].nalpha()
        m2_nalpha = self.wfn_cache[('m2', 'm', oldcalc.Z)].nalpha()
        m1_nbeta = self.wfn_cache[('m1', 'm', oldcalc.Z)].nbeta()
        m2_nbeta = self.wfn_cache[('m2', 'm', oldcalc.Z)].nbeta()

        Ca_d = np.zeros((m1_nso + m2_nso, m1_nso + m2_nso))
        Cb_d = np.zeros((m1_nso + m2_nso, m1_nso + m2_nso))
        if calc.V == 'm1':
            Ca_d[:m1_nso, :m1_nalpha] = Ca_occ
            Cb_d[:m1_nso, :m1_nbeta] = Cb_occ
        elif calc.V == 'm2':
            Ca_d[-m2_nso:, :m2_nalpha] = Ca_occ
            Cb_d[-m2_nso:, :m2_nbeta] = Cb_occ

        wfn_new = wfn.to_file()
        wfn_new['matrix']['Ca'] = Ca_d
        wfn_new['matrix']['Cb'] = Cb_d
        wfn_new['dimension']['nsopi'] = (m1_nso + m2_nso, )
        wfn_new['dimension']['nmopi'] = (m1_nso + m2_nso, )
        wfn_new['int']['nso'] = m1_nso + m2_nso
        wfn_new['int']['nmo'] = m1_nso + m2_nso

        wfn_new = core.Wavefunction.from_file(wfn_new)

        psi_scratch = core.IOManager.shared_object().get_default_path()
        write_filename = os.path.join(
            psi_scratch,
            os.path.split(
                os.path.abspath(core.get_writer_file_prefix(
                    self.fmt_ns(calc))))[1] + ".180.npy")
        wfn_new.to_file(write_filename)
        extras.register_numpy_file(write_filename)
        core.set_local_option('SCF', 'GUESS', 'READ')
Ejemplo n.º 5
0
    def _init_addghost_C(self, oldcalc, calc):
        # print('Adding ghost %s->%s' % (oldcalc, calc))

        old_filename = self._fmt_mo_fn(oldcalc)
        data = np.load(old_filename)
        Ca_occ = core.Matrix.np_read(data, "Ca_occ")
        Cb_occ = core.Matrix.np_read(data, "Cb_occ")

        m1_nso = self.wfn_cache[('m1', 'm', oldcalc.Z)].nso()
        m2_nso = self.wfn_cache[('m2', 'm', oldcalc.Z)].nso()
        m1_nalpha = self.wfn_cache[('m1', 'm', oldcalc.Z)].nalpha()
        m2_nalpha = self.wfn_cache[('m2', 'm', oldcalc.Z)].nalpha()
        m1_nbeta = self.wfn_cache[('m1', 'm', oldcalc.Z)].nbeta()
        m2_nbeta = self.wfn_cache[('m2', 'm', oldcalc.Z)].nbeta()

        if calc.V == 'm1':
            Ca_occ_d = core.Matrix('Ca_occ', (m1_nso + m2_nso), m1_nalpha)
            Ca_occ_d.np[:m1_nso, :] = Ca_occ.np[:, :]
            Cb_occ_d = core.Matrix('Cb_occ', (m1_nso + m2_nso), m1_nbeta)
            Cb_occ_d.np[:m1_nso, :] = Cb_occ.np[:, :]
        elif calc.V == 'm2':
            Ca_occ_d = core.Matrix('Ca_occ', (m1_nso + m2_nso), m2_nalpha)
            Ca_occ_d.np[-m2_nso:, :] = Ca_occ.np[:, :]

            Cb_occ_d = core.Matrix('Cb_occ', (m1_nso + m2_nso), m2_nbeta)
            Cb_occ_d.np[-m2_nso:, :] = Cb_occ.np[:, :]

        data_dict = dict(data)
        data_dict.update(Ca_occ_d.np_write(prefix='Ca_occ'))
        data_dict.update(Cb_occ_d.np_write(prefix='Cb_occ'))

        psi_scratch = core.IOManager.shared_object().get_default_path()
        write_filename = os.path.join(
            psi_scratch,
            os.path.split(
                os.path.abspath(core.get_writer_file_prefix(
                    self.fmt_ns(calc))))[1] + ".180.npz")
        np.savez(write_filename, **data_dict)
        extras.register_numpy_file(write_filename)
        core.set_local_option('SCF', 'GUESS', 'READ')
Ejemplo n.º 6
0
def run_json_qcschema(json_data, clean, json_serialization, keep_wfn=False):
    """
    An implementation of the QC JSON Schema (molssi-qc-schema.readthedocs.io/en/latest/index.html#) implementation in Psi4.


    Parameters
    ----------
    json_data : JSON
        Please see molssi-qc-schema.readthedocs.io/en/latest/spec_components.html for further details.

    Notes
    -----
    !Warning! This function is experimental and likely to change in the future.
    Please report any suggestions or uses of this function on github.com/MolSSI/QC_JSON_Schema.

    Examples
    --------

    """

    # Clean a few things
    _clean_psi_environ(clean)

    # This is currently a forced override
    if json_data["schema_name"] in ["qc_schema_input", "qcschema_input"]:
        json_data["schema_name"] = "qcschema_input"
    else:
        raise KeyError("Schema name of '{}' not understood".format(
            json_data["schema_name"]))

    if json_data["schema_version"] != 1:
        raise KeyError("Schema version of '{}' not understood".format(
            json_data["schema_version"]))

    if json_data.get("nthreads", False) is not False:
        core.set_num_threads(json_data["nthreads"], quiet=True)

    # Build molecule
    if "schema_name" in json_data["molecule"]:
        molschemus = json_data["molecule"]  # dtype >=2
    else:
        molschemus = json_data  # dtype =1
    mol = core.Molecule.from_schema(molschemus)

    # Update molecule geometry as we orient and fix_com
    json_data["molecule"]["geometry"] = mol.geometry().np.ravel().tolist()

    # Set options
    kwargs = json_data["keywords"].pop("function_kwargs", {})
    p4util.set_options(json_data["keywords"])

    # Setup the computation
    method = json_data["model"]["method"]
    core.set_global_option("BASIS", json_data["model"]["basis"])
    kwargs.update({"return_wfn": True, "molecule": mol})

    # Handle special properties case
    if json_data["driver"] == "properties":
        if "properties" not in kwargs:
            kwargs["properties"] = list(default_properties_)

    # Actual driver run
    val, wfn = methods_dict_[json_data["driver"]](method, **kwargs)

    # Pull out a standard set of SCF properties
    if "extras" not in json_data:
        json_data["extras"] = {}
    json_data["extras"]["qcvars"] = {}

    current_qcvars_only = json_data["extras"].get("current_qcvars_only", False)
    if json_data["extras"].get("wfn_qcvars_only", False):
        psi_props = wfn.variables(
            include_deprecated_keys=(not current_qcvars_only))
    else:
        psi_props = core.variables(
            include_deprecated_keys=(not current_qcvars_only))
        for k, v in psi_props.items():
            if k not in json_data["extras"]["qcvars"]:
                json_data["extras"]["qcvars"][k] = _serial_translation(
                    v, json=json_serialization)

    # Still a bit of a mess at the moment add in local vars as well.
    for k, v in wfn.variables().items():
        if k not in json_data["extras"]["qcvars"]:
            # interpreting wfn_qcvars_only as no deprecated qcvars either
            if not (json_data["extras"].get("wfn_qcvars_only", False) and
                    (any([
                        k.upper().endswith(" DIPOLE " + cart)
                        for cart in ["X", "Y", "Z"]
                    ]) or any([
                        k.upper().endswith(" QUADRUPOLE " + cart)
                        for cart in ["XX", "YY", "ZZ", "XY", "XZ", "YZ"]
                    ]) or k.upper() in [
                        "SOS-MP2 CORRELATION ENERGY",
                        "SOS-MP2 TOTAL ENERGY",
                        "SOS-PI-MP2 CORRELATION ENERGY",
                        "SOS-PI-MP2 TOTAL ENERGY",
                        "SCS-MP3 CORRELATION ENERGY",
                        "SCS-MP3 TOTAL ENERGY",
                    ])):
                json_data["extras"]["qcvars"][k] = _serial_translation(
                    v, json=json_serialization)

    # Handle the return result
    if json_data["driver"] == "energy":
        json_data["return_result"] = val
    elif json_data["driver"] in ["gradient", "hessian"]:
        json_data["return_result"] = _serial_translation(
            val, json=json_serialization)
    elif json_data["driver"] == "properties":
        ret = {}
        mtd = json_data["model"]["method"].upper()

        # Dipole/quadrupole still special case
        if "dipole" in kwargs["properties"]:
            ret["dipole"] = _serial_translation(psi_props[mtd + " DIPOLE"],
                                                json=json_serialization)
        if "quadrupole" in kwargs["properties"]:
            ret["quadrupole"] = _serial_translation(psi_props[mtd +
                                                              " QUADRUPOLE"],
                                                    json=json_serialization)
        ret.update(
            _convert_variables(wfn.variables(),
                               context="properties",
                               json=json_serialization))

        json_data["return_result"] = ret
    else:
        raise KeyError("Did not understand Driver key %s." %
                       json_data["driver"])

    props = {
        "calcinfo_nbasis": wfn.nso(),
        "calcinfo_nmo": wfn.nmo(),
        "calcinfo_nalpha": wfn.nalpha(),
        "calcinfo_nbeta": wfn.nbeta(),
        "calcinfo_natom": mol.geometry().shape[0],
        "nuclear_repulsion_energy": mol.nuclear_repulsion_energy(
        ),  # use this b/c psivar is monomer for SAPT
    }
    props.update(
        _convert_variables(psi_props,
                           context="generics",
                           json=json_serialization))
    if not list(
            set(['CBS NUMBER', 'NBODY NUMBER', 'FINDIF NUMBER'])
            & set(json_data["extras"]["qcvars"].keys())):
        props.update(
            _convert_variables(psi_props,
                               context="scf",
                               json=json_serialization))

    # Write out post-SCF keywords
    if "MP2 CORRELATION ENERGY" in psi_props:
        props.update(
            _convert_variables(psi_props,
                               context="mp2",
                               json=json_serialization))

    if "CCSD CORRELATION ENERGY" in psi_props:
        props.update(
            _convert_variables(psi_props,
                               context="ccsd",
                               json=json_serialization))

    if "CCSD(T) CORRELATION ENERGY" in psi_props:
        props.update(
            _convert_variables(psi_props,
                               context="ccsd(t)",
                               json=json_serialization))

    json_data["properties"] = props
    json_data["success"] = True
    json_data["provenance"]["module"] = wfn.module()

    if keep_wfn:
        json_data["wavefunction"] = _convert_wavefunction(wfn)

    files = {
        "psi4.grad":
        Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".grad"),
        "psi4.hess":
        Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".hess"),
        # binary "psi4.180.npy": Path(core.get_writer_file_prefix(wfn.molecule().name()) + ".180.npy"),
        "timer.dat":
        Path(
            "timer.dat"
        ),  # ok for `psi4 --qcschema` but no file collected for `qcengine.run_program(..., "psi4")`
    }
    json_data["native_files"] = {
        fl: flpath.read_text()
        for fl, flpath in files.items() if flpath.exists()
    }

    # Reset state
    _clean_psi_environ(clean)

    json_data["schema_name"] = "qcschema_output"

    return json_data
Ejemplo n.º 7
0
 def _fmt_mo_fn(self, calc):
     # type: (calcid,) -> str
     # Path to the molecular orbital file for a calc.
     fname = os.path.split(
         os.path.abspath(core.get_writer_file_prefix(self.fmt_ns(calc))))[1]
     return "%s.%s.npy" % (fname, psif.PSIF_SCF_MOS)
Ejemplo n.º 8
0
def _write_molden(
    self: core.Wavefunction,
    filename: Optional[str] = None,
    do_virtual: Optional[bool] = None,
    use_natural: bool = False,
):
    """Writes wavefunction information in *wfn* to *filename* in
    molden format. Will write natural orbitals from *density* (MO basis) if supplied.
    Warning! most post-SCF wavefunctions do not build the density as this is often
    much more costly than the energy. In addition, the wavefunction density attributes
    (Da and Db) return the SO density and must be transformed to the MO basis
    to use with this function.

    .. versionadded:: 0.5
       *wfn* parameter passed explicitly

    :returns: None

    :type filename:
    :param filename:

        Destination file name for MOLDEN file. If unspecified (None), a file
        name will be generated from the molecule name.

    :type do_virtual:
    :param do_virtual:

        Do write all the MOs to the MOLDEN file (True) or discard the unoccupied
        MOs (False). Not valid for NO's. If unspecified (None), value taken from
        :term:`MOLDEN_WITH_VIRTUAL <MOLDEN_WITH_VIRTUAL (GLOBALS)>`.

    :type use_natural:
    :param use_natural:

        Write natural orbitals determined from density on wavefunction.

    :examples:

    1. Molden file with the Kohn-Sham orbitals of a DFT calculation.

       >>> E, wfn = energy('b3lyp', return_wfn=True)
       >>> wfn.molden('mycalc.molden')

    2. Molden file with the natural orbitals of a CCSD computation. For correlated methods, an energy call will not compute the density.
       "properties" or "gradient" must be called.

       >>> E, wfn = properties('ccsd', return_wfn=True)
       >>> wfn.molden('ccsd_no.molden', use_natural=True)

    3. To supply a custom density matrix, manually set the Da and Db of the wavefunction.
       This is used, for example, to write natural orbitals coming from a root computed
       by a ``CIWavefunction`` computation, e.g., ``detci``, ``fci``, ``casscf``.
       The first two arguments of :py:meth:`~psi4.core.CIWavefunction.get_opdm`
       can be set to ``n, n`` where n => 0 selects the root to
       write out, provided these roots were computed, see :term:`NUM_ROOTS <NUM_ROOTS (DETCI)>`. The
       third argument controls the spin (``"A"``, ``"B"`` or ``"SUM"``) and the final
       boolean option determines whether inactive orbitals are included.

       >>> E, wfn = energy('detci', return_wfn=True)
       >>> wfn.Da() = wfn.get_opdm(0, 0, "A", True)
       >>> wfn.Db() = wfn.get_opdm(0, 0, "B", True)
       >>> molden(wfn, 'no_root1.molden', use_natural=True)

    """

    if filename is None:
        filename = core.get_writer_file_prefix(
            self.molecule().name()) + ".molden"

    if do_virtual is None:
        do_virtual = bool(core.get_option("SCF", "MOLDEN_WITH_VIRTUAL"))

    basisset = self.basisset()
    mol = self.molecule()
    # Header and geometry (Atom, Atom #, Z, x, y, z)
    mol_string = '[Molden Format]\n[Atoms] (AU)\n'
    for atom in range(mol.natom()):
        mol_string += f"{mol.symbol(atom):2s}  {atom+1:2d}  {int(mol.Z(atom)):3d}   {mol.x(atom):20.10f} {mol.y(atom):20.10f} {mol.z(atom):20.10f}\n"

    # Dump basis set
    mol_string += '[GTO]\n'
    for atom in range(mol.natom()):
        mol_string += f"  {atom+1:d} 0\n"
        for rel_shell_idx in range(basisset.nshell_on_center(atom)):
            abs_shell_idx = basisset.shell_on_center(atom, rel_shell_idx)
            shell = basisset.shell(abs_shell_idx)
            mol_string += f" {shell.amchar:s}{shell.nprimitive:5d}  1.00\n"
            for prim in range(shell.nprimitive):
                mol_string += f"{shell.exp(prim):20.10f} {shell.original_coef(prim):20.10f}\n"
        mol_string += '\n'

    #
    if use_natural:
        # Alphas
        nmopi = self.nmopi()
        #MO_Da = core.Matrix("MO Alpha Density Matrix", nmopi, nmopi)
        #MO_Da.transform(self.Da(), self.Ca().transpose())
        MO_Da = self.Da_subset("MO")  #MO_Da.transform(self.Da(), self.Ca())
        NO_Ra = core.Matrix("NO Alpha Rotation Matrix", nmopi, nmopi)
        occupation_a = core.Vector(nmopi)
        MO_Da.diagonalize(NO_Ra, occupation_a,
                          core.DiagonalizeOrder.Descending)
        Ca = core.doublet(self.Ca(), NO_Ra, False, False)
        epsilon_a = occupation_a
        # Betas
        #MO_Db = core.Matrix("MO Beta Density Matrix", nmopi, nmopi)
        #MO_Db.transform(self.Db(), self.Cb().transpose())
        MO_Db = self.Db_subset("MO")
        NO_Rb = core.Matrix("NO Beta Rotation Matrix", nmopi, nmopi)
        occupation_b = core.Vector(nmopi)
        MO_Db.diagonalize(NO_Rb, occupation_b,
                          core.DiagonalizeOrder.Descending)
        Cb = core.doublet(self.Cb(), NO_Rb, False, False)
        epsilon_b = occupation_b

    else:
        Ca = self.Ca()
        Cb = self.Cb()
        occupation_a = self.occupation_a()
        occupation_b = self.occupation_b()
        epsilon_a = self.epsilon_a()
        epsilon_b = self.epsilon_b()

    # Convert C matrices to AO MO basis. Ca_subset costs information about which symmetry an orbital originally had, which is why we can't use it.
    aotoso = self.aotoso()
    Ca_ao_mo = core.doublet(aotoso, Ca, False, False).nph
    Cb_ao_mo = core.doublet(aotoso, Cb, False, False).nph
    ao_overlap = self.mintshelper().ao_overlap().np
    # Convert from Psi4 internal normalization to the unit normalization expected by Molden
    ao_normalizer = ao_overlap.diagonal()**(-1 / 2)
    Ca_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T
                                       for i in Ca_ao_mo])
    Cb_ao_mo = core.Matrix.from_array([(i.T / ao_normalizer).T
                                       for i in Cb_ao_mo])

    # Reorder AO x MO matrix to fit Molden conventions
    '''
    Reordering expected by Molden
    P: x, y, z
    5D: D 0, D+1, D-1, D+2, D-2
    6D: xx, yy, zz, xy, xz, yz
    7F: F 0, F+1, F-1, F+2, F-2, F+3, F-3
    10F: xxx, yyy, zzz, xyy, xxy, xxz, xzz, yzz, yyz, xyz
    9G: G 0, G+1, G-1, G+2, G-2, G+3, G-3, G+4, G-4
    15G: xxxx, yyyy, zzzz, xxxy, xxxz, yyyz, zzzx, zzzy, xxyy, xxzz, yyzz, xxyz, yyxz, zzxy
    
    Molden does not handle angular momenta higher than G
    '''
    molden_cartesian_order = [
        [2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # p
        [0, 3, 4, 1, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0],  # d
        [0, 4, 5, 3, 9, 6, 1, 8, 7, 2, 0, 0, 0, 0, 0],  # f
        [0, 3, 4, 9, 12, 10, 5, 13, 14, 7, 1, 6, 11, 8, 2]  # g
    ]
    nirrep = self.nirrep()
    count = 0  # Keeps track of count for reordering
    temp_a = Ca_ao_mo.clone()  # Placeholders for original AO x MO matrices
    temp_b = Cb_ao_mo.clone()

    for i in range(basisset.nshell()):
        am = basisset.shell(i).am
        if (am == 1 and basisset.has_puream()) or (
                am > 1 and am < 5 and basisset.shell(i).is_cartesian()):
            for j in range(basisset.shell(i).nfunction):
                for h in range(nirrep):
                    for k in range(Ca_ao_mo.coldim()[h]):
                        Ca_ao_mo.set(h,
                                     count + molden_cartesian_order[am - 1][j],
                                     k, temp_a.get(h, count + j, k))
                        Cb_ao_mo.set(h,
                                     count + molden_cartesian_order[am - 1][j],
                                     k, temp_b.get(h, count + j, k))
        count += basisset.shell(i).nfunction

    # Dump MO information
    if basisset.has_puream():
        # For historical reasons, D and F can go on the same line, but setting D without F implicitly sets F. G must be on its own.
        mol_string += '[5D7F]\n[9G]\n\n'
    ct = mol.point_group().char_table()
    mol_string += '[MO]\n'
    mo_dim = self.nmopi() if do_virtual else (self.doccpi() + self.soccpi())

    # Alphas. If Alphas and Betas are the same, then only Alphas with double occupation will be written (see line marked "***")
    mos = []
    for h in range(nirrep):
        for n in range(mo_dim[h]):
            mos.append((epsilon_a.get(h, n), (h, n)))

    # Sort mos based on energy
    def mosSort(element):
        return element[0]

    mos.sort(key=mosSort)

    for i in range(len(mos)):
        h, n = mos[i][1]
        mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_a.get(h, n):24.10e}\n Spin= Alpha\n"
        if self.same_a_b_orbs() and self.epsilon_a() == self.epsilon_b(
        ) and self.same_a_b_dens():
            mol_string += f" Occup= {occupation_a.get(h, n) + occupation_b.get(h, n):24.10e}\n"
        else:
            mol_string += f" Occup= {occupation_a.get(h, n):24.10e}\n"
        for so in range(self.nso()):
            mol_string += f"{so+1:3d} {Ca_ao_mo.get(h, so, n):24.10e}\n"

    # Betas
    mos = []
    if not self.same_a_b_orbs(
    ) or self.epsilon_a() != self.epsilon_b() or not self.same_a_b_dens():
        for h in range(nirrep):
            for n in range(mo_dim[h]):
                mos.append((self.epsilon_b().get(h, n), (h, n)))
        mos.sort(key=mosSort)
        for i in range(len(mos)):
            h, n = mos[i][1]
            mol_string += f" Sym= {ct.gamma(h).symbol():s}\n Ene= {epsilon_b.get(h, n):24.10e}\n Spin= Beta\n " \
                          f"Occup= {occupation_b.get(h, n):24.10e}\n"
            for so in range(self.nso()):
                mol_string += f"{so+1:3d} {Cb_ao_mo.get(h, so, n):24.10e}\n"

    # Write Molden string to file
    with open(filename, 'w') as fn:
        fn.write(mol_string)
Ejemplo n.º 9
0
def _core_wavefunction_get_scratch_filename(self, filenumber):
    """ Given a wavefunction and a scratch file number, canonicalizes the name
        so that files can be consistently written and read """
    fname = os.path.split(os.path.abspath(core.get_writer_file_prefix(self.molecule().name())))[1]
    psi_scratch = core.IOManager.shared_object().get_default_path()
    return os.path.join(psi_scratch, fname + '.' + str(filenumber))