Source code for gemseo_scilab.py_scilab

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""Scilab wrapper."""
from __future__ import annotations

import logging
import re
from pathlib import Path
from typing import Any
from typing import Callable
from typing import Final
from typing import Sequence

from numpy import ndarray
from scilab2py import scilab

LOGGER = logging.getLogger(__name__)


[docs]class ScilabFunction: """A scilab function.""" _f_pointer: Callable | None _fun_def: str name: str args: Sequence[str] outs: Sequence[str] def __init__( self, fun_def: str, name: str, args: Sequence[str], outs: Sequence[str], ) -> None: """Constructor. Args: fun_def: The definition of the function. name: The name of the function. args: The arguments of the function. outs: The outputs of the function. """ self._f_pointer = None self._fun_def = fun_def self.name = name self.args = args self.outs = outs self.__init_from_def() def __call__( # noqa: D102 self, *args: Any, **kwargs: Any ) -> dict[str, float | ndarray]: return self._f_pointer(*args, **kwargs) def __init_from_def(self) -> None: """Initialize the function from its definition.""" exec(self._fun_def) self._f_pointer = locals()[self.name] def __getstate__(self) -> dict[str, Any]: out_dict = self.__dict__.copy() del out_dict["_f_pointer"] return out_dict def __setstate__(self, state: dict[str, Any]) -> None: self.__dict__.update(state) self._f_pointer = None self.__init_from_def()
[docs]class ScilabPackage: """Interface to a scilab package. Scilab python interface scans the sci files in a directory and generates python functions from them. """ RE_OUTS: Final[re.Pattern] = re.compile(r"\[([^$].*?)]") RE_FUNC: Final[re.Pattern] = re.compile(r"=([^$].*?)\(") RE_ARGS: Final[re.Pattern] = re.compile(r"\(([^$].*?)\)") def __init__(self, script_dir_path: str | Path) -> None: """Constructor. Args: script_dir_path: The path to the directory to scan for .sci files. Raises: FileNotFoundError: If the `script_dir_path` does not exist. """ script_dir_path = Path(script_dir_path) if not script_dir_path.is_dir(): raise FileNotFoundError( f"Script directory for Scilab sources: {script_dir_path}" " does not exist." ) # scilab.timeout = 10 LOGGER.info("Using the scilab script directory: %s", script_dir_path) scilab.getd(str(script_dir_path)) self.functions = {} self.__scan_funcs(script_dir_path) def __scan_onef(self, line: str) -> None: """Scan a function in a sci file to parse its arguments, outputs and name. Args: line: The line from the sci file to scan. Raises: ValueError: If no function is found in `line`. If the function has no outputs. If the function has no arguments. """ line = line.replace(" ", "") match_groups = self.RE_FUNC.search(line) if match_groups is None: raise ValueError(f"No function name found in {line}") fname = match_groups.group(0).strip()[1:-1].strip() LOGGER.debug("Detected function: %s", fname) match_groups = self.RE_OUTS.search(line) if match_groups is None: raise ValueError(f"Function {fname} has no outputs.") argstr = match_groups.group(0).strip() argstr = argstr.replace("[", "").replace("]", "") outs = argstr.split(",") fouts = [out_str.strip() for out_str in outs] LOGGER.debug("Outputs are: %s", outs) match_groups = self.RE_ARGS.search(line) if match_groups is None: raise ValueError(f"Function {fname} has no arguments.") argstr = match_groups.group(0).strip()[1:-1].strip() args = argstr.split(",") fargs = [args_str.strip() for args_str in args] LOGGER.debug("And arguments are: %s", args) args_form = ", ".join(fargs) outs_form = ", ".join(fouts) fun_def = f""" def {fname}({args_form}): '''Auto generated function from scilab. name: {fname} arguments: {args_form} outputs: {outs_form} ''' {outs_form} = scilab.{fname}({args_form}) return {outs_form} """ self.functions[fname] = ScilabFunction(fun_def, fname, fargs, fouts) def __scan_funcs(self, script_dir_path: Path) -> None: """Scan all functions in the directory. Raises: ValueError: If an interface cannot be generated for a function. """ for script_f in script_dir_path.glob("*.sci"): LOGGER.info("Found script file: %s", script_f) with open(script_f) as script: for line in script.readlines(): if not line.strip().startswith("function"): continue try: self.__scan_onef(line) except ValueError: LOGGER.error("Cannot generate interface for function %s", line) raise def __str__(self) -> str: sout = "Scilab python interface\nAvailable functions:\n" for function in self.functions.values(): sout += function._f_pointer.__doc__ return sout