Source code for VeraGridEngine.Utils.NumericalMethods.emt_sparse_solver_loader

# 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/.
# SPDX-License-Identifier: MPL-2.0

from __future__ import annotations

import importlib.util
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict

from VeraGridEngine.Utils.NumericalMethods.external_sparse_solver_interface import SparseLinearSolverBackendProvider
from VeraGridEngine.enumerations import SparseSolver


[docs] class SparseSolverPluginManifest: """ Parsed manifest of one external EMT sparse solver plugin. """ __slots__ = ["_name", "_solver_type", "_module_name", "_class_name", "_version"] def __init__(self, name: str, solver_type: SparseSolver, module_name: str, class_name: str, version: str) -> None: """ Build the plugin manifest object. :param name: Plugin name. :type name: str :param solver_type: Sparse solver type exposed by the plugin. :type solver_type: SparseSolver :param module_name: Python module file name without extension. :type module_name: str :param class_name: Provider class name. :type class_name: str :param version: Plugin version string. :type version: str :return: None. :rtype: None """ self._name: str = name self._solver_type: SparseSolver = solver_type self._module_name: str = module_name self._class_name: str = class_name self._version: str = version
[docs] def get_name(self) -> str: """ Return the plugin name. :return: Plugin name. :rtype: str """ return self._name
[docs] def get_solver_type(self) -> SparseSolver: """ Return the sparse solver type. :return: Sparse solver type. :rtype: SparseSolver """ return self._solver_type
[docs] def get_module_name(self) -> str: """ Return the module name. :return: Module name. :rtype: str """ return self._module_name
[docs] def get_class_name(self) -> str: """ Return the provider class name. :return: Provider class name. :rtype: str """ return self._class_name
[docs] def get_version(self) -> str: """ Return the plugin version string. :return: Plugin version string. :rtype: str """ return self._version
[docs] def get_default_sparse_solver_plugins_directory() -> Path: """ Return the default directory containing EMT sparse solver plugins. :return: Default sparse-solver plugin directory. :rtype: Path """ plugin_root: Path = Path.home() / ".VeraGrid" / "plugins" / "sparse_solvers" os.makedirs(plugin_root, exist_ok=True) return plugin_root
[docs] def resolve_sparse_solver_plugins_directory(custom_directory: str) -> Path: """ Resolve the sparse-solver plugin directory. :param custom_directory: User-provided plugin directory. :type custom_directory: str :return: Resolved plugin directory. :rtype: Path """ if len(custom_directory) > 0: return Path(custom_directory) else: return get_default_sparse_solver_plugins_directory()
[docs] def read_sparse_solver_plugin_manifest(plugin_directory: Path) -> SparseSolverPluginManifest: """ Read and validate one sparse-solver plugin manifest. :param plugin_directory: Plugin directory. :type plugin_directory: Path :return: Parsed manifest. :rtype: SparseSolverPluginManifest """ manifest_path: Path = plugin_directory / "plugin.json" if manifest_path.exists(): pass else: raise FileNotFoundError(f"Sparse solver plugin manifest not found: {manifest_path}") raw_data_any: Any with open(manifest_path, "r", encoding="utf-8") as json_file: raw_data_any = json.load(json_file) if isinstance(raw_data_any, dict): raw_data: Dict[str, Any] = dict(raw_data_any) else: raise TypeError(f"Sparse solver plugin manifest must be a JSON object: {manifest_path}") if "name" in raw_data and "solver_type" in raw_data and "module" in raw_data and "class_name" in raw_data and "version" in raw_data: solver_type: SparseSolver = SparseSolver[str(raw_data["solver_type"])] return SparseSolverPluginManifest( name=str(raw_data["name"]), solver_type=solver_type, module_name=str(raw_data["module"]), class_name=str(raw_data["class_name"]), version=str(raw_data["version"]), ) else: raise ValueError(f"Sparse solver plugin manifest is missing required fields: {manifest_path}")
[docs] def load_sparse_solver_backend_provider_from_plugin( plugin_directory: Path, manifest: SparseSolverPluginManifest, ) -> SparseLinearSolverBackendProvider: """ Load a sparse-solver backend provider from one plugin directory. :param plugin_directory: Plugin directory. :type plugin_directory: Path :param manifest: Parsed plugin manifest. :type manifest: SparseSolverPluginManifest :return: Sparse solver backend provider. :rtype: SparseLinearSolverBackendProvider """ module_path: Path = plugin_directory / f"{manifest.get_module_name()}.py" spec = importlib.util.spec_from_file_location( f"emt_sparse_plugin_{manifest.get_name()}_{manifest.get_version()}", module_path, ) if spec is None: raise RuntimeError(f"Unable to create import spec for sparse solver plugin: {module_path}") else: pass if spec.loader is None: raise RuntimeError(f"Sparse solver plugin spec has no loader: {module_path}") else: pass module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) if manifest.get_class_name() in module.__dict__: provider_class: Any = module.__dict__[manifest.get_class_name()] else: raise AttributeError( f"Sparse solver plugin provider class '{manifest.get_class_name()}' not found in {module_path}" ) provider_obj: Any = provider_class() if isinstance(provider_obj, SparseLinearSolverBackendProvider): return provider_obj else: raise TypeError( f"Sparse solver plugin provider '{manifest.get_class_name()}' does not implement SparseLinearSolverBackendProvider" )
[docs] def load_sparse_solver_backend_provider( plugin_name: str, plugin_directory_override: str, ) -> SparseLinearSolverBackendProvider: """ Load one sparse-solver backend provider by plugin name. :param plugin_name: Plugin name. :type plugin_name: str :param plugin_directory_override: Optional plugin-directory override. :type plugin_directory_override: str :return: Sparse solver backend provider. :rtype: SparseLinearSolverBackendProvider """ root_directory: Path = resolve_sparse_solver_plugins_directory(plugin_directory_override) plugin_directory: Path = root_directory / plugin_name manifest: SparseSolverPluginManifest = read_sparse_solver_plugin_manifest(plugin_directory) return load_sparse_solver_backend_provider_from_plugin(plugin_directory, manifest)