Skip to content

Commit

Permalink
Adds code to build ratMain extension (#21)
Browse files Browse the repository at this point in the history
* Add code for rat_core, events, custom wrapper, setup.py and pyproject.toml
  • Loading branch information
StephenNneji authored Feb 23, 2024
1 parent 8732a27 commit 129b351
Show file tree
Hide file tree
Showing 12 changed files with 1,977 additions and 9 deletions.
25 changes: 19 additions & 6 deletions .github/workflows/runTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
version: ["3.9", "3.x"]
defaults:
run:
shell: bash -l {0}

runs-on: ${{ matrix.os }}

Expand All @@ -28,11 +31,21 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.version }}
- name: Install dependencies
- name: Install OMP (MacOS)
if: runner.os == 'macOS'
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Test with pytest
brew install llvm libomp
echo "export CC=/usr/local/opt/llvm/bin/clang" >> ~/.bashrc
echo "export CFLAGS=\"$CFLAGS -I/usr/local/opt/libomp/include\"" >> ~/.bashrc
echo "export CXXFLAGS=\"$CXXFLAGS -I/usr/local/opt/libomp/include\"" >> ~/.bashrc
echo "export LDFLAGS=\"$LDFLAGS -Wl,-rpath,/usr/local/opt/libomp/lib -L/usr/local/opt/libomp/lib -lomp\"" >> ~/.bashrc
source ~/.bashrc
- name: Install OMP (Linux)
if: runner.os == 'Linux'
run: |
pip install pytest pytest-cov
pytest tests/ --cov=${{ github.event.repository.name }} --cov-report=html:htmlcov
sudo apt-get update
sudo apt install libomp-dev
- name: Install and Test with pytest
run: |
python -m pip install -e .[Dev]
pytest tests/ --cov=${{ github.event.repository.name }} --cov-report=html:htmlcov --cov-report=term
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,16 @@ htmlcov/
# Build
_build
docs/.buildinfo
docs/*.inv
docs/*.inv
*.rat.cpp
*.so
*.dll
*.dylib
*.obj
*.exp
*.lib
*.a
*.egg-info/
build/*
dist/*
*.whl
5 changes: 5 additions & 0 deletions RAT/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import os
from RAT.classlist import ClassList
from RAT.project import Project
import RAT.controls
import RAT.models


dir_path = os.path.dirname(os.path.realpath(__file__))
os.environ["RAT_PATH"] = os.path.join(dir_path, '')
63 changes: 63 additions & 0 deletions RAT/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import Callable, Union, List
import RAT.rat_core
from RAT.rat_core import EventTypes, PlotEventData


def notify(event_type: EventTypes, data: Union[str, PlotEventData]) -> None:
"""Calls registered callbacks with the data when event type has
been triggered.
Parameters
----------
event_type : EventTypes
The event type that was triggered.
data : str or PlotEventData
The data sent by the event. The message event data is a string.
"""
callbacks = __event_callbacks[event_type]
for callback in callbacks:
callback(data)

def get_event_callback(event_type: EventTypes) -> List[Callable[[Union[str, PlotEventData]], None]]:
"""Returns all callbacks registered for the given event type.
Parameters
----------
event_type : EventTypes
The event type.
Retuns
------
callback : Callable[[Union[str, PlotEventData]], None]
The callback for the event type.
"""
return list(__event_callbacks[event_type])


def register(event_type: EventTypes, callback: Callable[[Union[str, PlotEventData]], None]) -> None:
"""Registers a new callback for the event type.
Parameters
----------
event_type : EventTypes
The event type to register.
callback : Callable[[Union[str, PlotEventData]], None]
The callback for when the event is triggered.
"""
if not isinstance(event_type, EventTypes):
raise ValueError("event_type must be a events.EventTypes enum")

if len(__event_callbacks[event_type]) == 0:
__event_impl.register(event_type)
__event_callbacks[event_type].add(callback)


def clear() -> None:
"""Clears all event callbacks."""
__event_impl.clear()
for key in __event_callbacks.keys():
__event_callbacks[key] = set()


__event_impl = RAT.rat_core.EventBridge(notify)
__event_callbacks = {EventTypes.Message: set(), EventTypes.Plot: set()}
84 changes: 84 additions & 0 deletions RAT/misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import pathlib
from typing import Callable, Tuple
import numpy as np
from numpy.typing import ArrayLike
import RAT.rat_core


class MatlabWrapper:
"""Creates a python callback for a MATLAB function.
Parameters
----------
filename : string
The path of the file containing MATLAB function
"""
def __init__(self, filename: str) -> None :
self.engine = None
try:
import matlab.engine
except ImportError:
raise ImportError('matlabengine is required to use MatlabWrapper')
self.engine = matlab.engine.start_matlab()
path = pathlib.Path(filename)
self.engine.cd(str(path.parent), nargout=0)
self.function_name = path.stem

def __del__(self):
if self.engine is not None:
self.engine.quit()

def getHandle(self)\
-> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]:
"""Returns a wrapper for the custom MATLAB function
Returns
-------
wrapper : Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]
The wrapper function for the MATLAB callback
"""
def handle(params, bulk_in, bulk_out, contrast, domain=-1):
if domain == -1:
output, sub_rough = getattr(self.engine, self.function_name)(np.array(params, 'float'),
np.array(bulk_in, 'float'),
np.array(bulk_out, 'float'),
float(contrast + 1), nargout=2)
else:
output, sub_rough = getattr(self.engine, self.function_name)(np.array(params, 'float'),
np.array(bulk_in, 'float'),
np.array(bulk_out, 'float'),
float(contrast + 1), float(domain + 1), nargout=2)
return output, sub_rough
return handle


class DylibWrapper:
"""Creates a python callback for a function in dynamic library.
Parameters
----------
filename : str
The path of the dyanamic library
function_name : str
The name of the function to call
"""
def __init__(self, filename, function_name) -> None:
self.engine = RAT.rat_core.DylibEngine(filename, function_name)

def getHandle(self)\
-> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]:

"""Returns a wrapper for the custom dynamic library function
Returns
-------
wrapper : Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], Tuple[ArrayLike, float]]
The wrapper function for the dynamic library callback
"""
def handle(params, bulk_in, bulk_out, contrast, domain=-1):
if domain == -1:
output, sub_rough = self.engine.invoke(params, bulk_in, bulk_out, contrast)
else:
output, sub_rough = self.engine.invoke(params, bulk_in, bulk_out, contrast, domain)
return output, sub_rough
return handle
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
python-RAT
==========
Python-RAT is the Python interface for the [Reflectivity Algorithm Toolbox](https://github.com/RascalSoftware/RAT) (RAT).
Python-RAT is the Python interface for the [Reflectivity Algorithm Toolbox](https://github.com/RascalSoftware/RAT) (RAT).

Install
=======
To install in local directory:

pip install -e .

matlabengine is an optional dependency only required for Matlab custom functions. The version of matlabengine should match the version of Matlab installed on the machine. This can be installed as shown below:
pip install -e .[Matlab-2023a]

Development dependencies can be installed as shown below
pip install -e .[Dev]

To build wheel:
pip install build
python -m build --wheel
Loading

0 comments on commit 129b351

Please sign in to comment.