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 glob import glob
from pathlib import Path
from typing import Sequence

from numpy import ndarray
from scilab2py import scilab

LOGGER = logging.getLogger(__name__)


[docs]class ScilabFunction: """A scilab function.""" 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__(self, *args, **kwargs) -> dict[str, float | ndarray]: """Call the scilab function.""" 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): out_dict = self.__dict__.copy() del out_dict["_f_pointer"] return out_dict def __setstate__(self, state): 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 = re.compile(r"\[([^$].*?)]") RE_FUNC = re.compile(r"=([^$].*?)\(") RE_ARGS = re.compile(r"\(([^$].*?)\)") def __init__(self, script_dir_path: str) -> 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. """ if not Path(script_dir_path).exists(): raise FileNotFoundError( f"Script directory for Scilab sources: {script_dir_path}" " does not exist." ) # scilab.timeout = 10 LOGGER.info("Use scilab script directory: %s", script_dir_path) self.__script_dir_path = script_dir_path scilab.getd(str(script_dir_path)) self.functions = {} self.__scan_funcs() 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) -> None: """Scan all functions in the directory. Raises: ValueError: If an interface cannot be generated for a function. """ for script_f in glob(str(Path(self.__script_dir_path) / "*.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