# SPDX-FileCopyrightText: 2024 PairInteraction Developers
# SPDX-License-Identifier: LGPL-3.0-or-later
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, ClassVar
from pairinteraction import _backend
from pairinteraction.custom_logging import _flush_pending_logs
if TYPE_CHECKING:
import os
from pathlib import Path
logger = logging.getLogger(__name__)
[docs]
class Database:
"""Class for handling the databases for the PairInteraction package.
The Databases are used to store the atomic states, their energies, and their matrix elements to other states.
The databases are stored in the user's cache directory by default, but can be stored in a different directory.
When running PairInteraction for the first time, the databases have to be downloaded from the internet
(e.g. by explicitly passing `download_missing=True` to the constructor).
Once the databases are downloaded, the user usually does not have to interact with the Database class directly.
"""
_global_database: ClassVar[Database | None] = None
[docs]
def __init__(
self,
download_missing: bool = False,
use_cache: bool = True,
database_dir: str | os.PathLike[str] = "",
) -> None:
"""Create a new database instance with the given parameters.
Args:
download_missing: Whether to download missing databases if needed. Default False.
use_cache: Whether to load the Wigner 3j symbols table into memory. Default True.
database_dir: The directory where the databases are stored.
Default "", i.e. use the default directory (the user's cache directory).
"""
self._cpp = _backend.Database(download_missing, use_cache, database_dir)
_flush_pending_logs() # call it manually because constructors of classes from nanobind cannot be decorated
@classmethod
def _from_cpp_object(cls, cpp_obj: _backend.Database) -> Database:
"""Create a Database instance from a C++ Database object.
This is used internally to convert C++ objects returned by the C++ API to Python objects.
"""
obj = cls.__new__(cls)
obj._cpp = cpp_obj
return obj
[docs]
@classmethod
def get_global_database(cls) -> Database:
"""Return the global database instance if it was initialized, otherwise None."""
return cls._global_database # type: ignore [return-value]
[docs]
@classmethod
def initialize_global_database(
cls,
download_missing: bool = False,
use_cache: bool = True,
database_dir: str | os.PathLike[str] = "",
) -> None:
"""Initialize the global database with the given parameters.
The arguments are the same as for the constructor of this class.
"""
db = cls(download_missing, use_cache, database_dir)
if cls._global_database is None:
cls._global_database = db
elif (
cls._global_database.download_missing == db.download_missing
and cls._global_database.use_cache == db.use_cache
and cls._global_database.database_dir == db.database_dir
):
pass # already initialized with the same parameters
else:
raise ValueError(
"Global database was already initialized with different parameters. "
"The global database is automatically initialized when needed. "
"If you explicitly want to initialize the global database, do this at the beginning of your script."
)
@property
def download_missing(self) -> bool:
"""Whether to download missing databases if needed."""
return self._cpp.get_download_missing()
@property
def use_cache(self) -> bool:
"""Whether to load the Wigner 3j symbols table into memory."""
return self._cpp.get_use_cache()
@property
def database_dir(self) -> Path:
"""The directory where the databases are stored."""
return self._cpp.get_database_dir()
[docs]
def get_versions_info(self) -> str:
"""Return a formatted table of local and remote database versions."""
return self._cpp.get_versions_info()
def print_database_info(
download_missing: bool = True,
use_cache: bool = True,
database_dir: str | os.PathLike[str] = "",
) -> None:
"""Print an overview of local and remote database tables."""
db = Database(download_missing, use_cache, database_dir)
print(f"Database directory: {db.database_dir}")
print(db.get_versions_info())