Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ratapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ratapi.outputs import BayesResults, Results
from ratapi.project import Project
from ratapi.run import run
from ratapi.utils import convert, plotting
from ratapi.utils import convert, matlab, plotting

with suppress(ImportError): # orsopy is an optional dependency
from ratapi.utils import orso as orso
Expand All @@ -26,4 +26,5 @@
"run",
"plotting",
"convert",
"matlab",
]
29 changes: 20 additions & 9 deletions ratapi/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,16 +927,14 @@ def classlist_script(name, classlist):
+ "\n)"
)

def save(self, filepath: str | Path = "./project.json"):
"""Save a project to a JSON file.
def model_dump(self):
"""Generate a dictionary representation of the model.

Parameters
----------
filepath : str or Path
The path to where the project file will be written.
Returns
-------
json_dict : dict
A dict containing the model information.
"""
filepath = Path(filepath).with_suffix(".json")

json_dict = {}
for field in self.model_fields:
attr = getattr(self, field)
Expand All @@ -960,7 +958,7 @@ def make_custom_file_dict(item):
"name": item.name,
"filename": item.filename,
"language": item.language,
"path": try_relative_to(item.path, filepath.parent),
"path": str(item.path),
}
if item.name != item.function_name:
file_dict["function_name"] = item.function_name
Expand All @@ -973,7 +971,20 @@ def make_custom_file_dict(item):
json_dict[field] = [item.model_dump() for item in attr]
else:
json_dict[field] = attr
return json_dict

def save(self, filepath: str | Path = "./project.json"):
"""Save a project to a JSON file.

Parameters
----------
filepath : str or Path
The path to where the project file will be written.
"""
filepath = Path(filepath).with_suffix(".json")
json_dict = self.model_dump()
for file in json_dict["custom_files"]:
file["path"] = try_relative_to(file["path"], filepath.parent)
filepath.write_text(json.dumps(json_dict))

@classmethod
Expand Down
115 changes: 115 additions & 0 deletions ratapi/utils/matlab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Runs RAT from the MATLAB API."""

import json
import tempfile
import warnings
from pathlib import Path

from ..outputs import Results
from ..project import Project
from ..wrappers import MatlabWrapper

RUNNER = """function executeRAT()

cur_dir = pwd;
cd('{rat_path}');
addPaths;
cd(cur_dir);

project = jsonToProject('{project}');
controls = jsonToControls('{control}');
customControls = customControl();
customControls.update(controls);
customControls.filePath = '{ipc_path}';
if any(strcmpi(controls.procedure, {{procedures.DE.value, procedures.Simplex.value}}))
useLivePlot(1);
end
for i=1:project.customFile.rowCount
addpath(project.customFile.varTable{{i, 5}});
end
[project, results] = RAT(project, customControls);

projectToJson(project, '{project}');
resultsToJson(results, '{result}');
close all
end
"""


CONTROL = """classdef customControl < controlsClass
properties(Hidden = true)
filePath = ''
end
methods
function update(obj, controls)
propNames = properties(controls);
for i = 1:length(propNames)
obj.(propNames{i}) = controls.(propNames{i});
end
end
function path = getIPCFilePath(obj)
path = obj.filePath;
end
end
end
"""


def run_matlab_directly(project, controls, matlab_rat_path, ipc_path="", stdout=None, stderr=None):
"""Run User provided MATLAB RAT for the given project and controls inputs.

Parameters
----------
project : RAT.Project or dict
The project model (or equivalent json dict), which defines the physical system under study.
controls : RAT.Controls or dict
The controls model (or equivalent json dict), which defines algorithmic properties.
matlab_rat_path : str
The path to MATLAB RAT folder.
ipc_path : str, optional
IPC path for MATLAB to use
stdout : io.TextIOBase, optional
Text stream for MATLAB console output
stderr : io.TextIOBase, optional
Text stream for MATLAB console error output
"""
if MatlabWrapper.loader is None:
raise ImportError(MatlabWrapper.loader_error_message) from None

engine = MatlabWrapper.loader.result()

with tempfile.TemporaryDirectory() as tmp:
project_file = Path(tmp, "project.json")
control_file = Path(tmp, "controls.json")
result_file = Path(tmp, "results.json")
runner_file = Path(tmp, "executeRAT.m")
custom_controls_file = Path(tmp, "customControl.m")
with open(custom_controls_file, "w") as f:
f.write(CONTROL)

with open(runner_file, "w") as f:
f.write(
RUNNER.format(
project=project_file,
control=control_file,
result=result_file,
rat_path=matlab_rat_path,
ipc_path=ipc_path,
)
)

controls.save(control_file) if not isinstance(controls, dict) else control_file.write_text(json.dumps(controls))

with warnings.catch_warnings(): # Avoid warning about relative paths
warnings.simplefilter("ignore")
project.save(project_file) if not isinstance(project, dict) else project_file.write_text(
json.dumps(project)
)

engine.addpath(tmp, nargout=0)
engine.executeRAT(nargout=0, stdout=stdout, stderr=stderr)
engine.rmpath(tmp, nargout=0)

project = Project.load(project_file)
results = Results.load(result_file)
return project, results
Loading