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