Skip to content

Commit

Permalink
Remove pythonHome parameter (#19)
Browse files Browse the repository at this point in the history
and update documentation

fixes #15
fixes #17
  • Loading branch information
t-sommer authored May 27, 2024
1 parent 9b90a61 commit 1f6466e
Show file tree
Hide file tree
Showing 11 changed files with 33 additions and 70 deletions.
4 changes: 2 additions & 2 deletions C/include/ExternalLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
extern "C" {
#endif

EXPORT const char* externalFunction(const char *filename, const char *moduleName, const char *functionName, const char *pythonHome, int nu, const double u[], int ny, double y[]);
EXPORT const char* externalFunction(const char *filename, const char *moduleName, const char *functionName, int nu, const double u[], int ny, double y[]);

EXPORT void* createExternalObject(const char *filename, const char *moduleName, const char *className, const char *pythonHome, const ModelicaUtilityFunctions_t *callbacks);
EXPORT void* createExternalObject(const char *filename, const char *moduleName, const char *className, const ModelicaUtilityFunctions_t *callbacks);

EXPORT void evaluateExternalObject(void *externalObject, int nu, const double u[], int ny, double y[]);

Expand Down
6 changes: 2 additions & 4 deletions C/src/ExternalLibraryCPP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@

static ModelicaUtilityFunctions_t s_callbacks = { NULL };

const char* externalFunction(const char *filename, const char *moduleName, const char *functionName, const char *pythonHome, int nu, const double u[], int ny, double y[]) {
const char* externalFunction(const char *filename, const char *moduleName, const char *functionName, int nu, const double u[], int ny, double y[]) {

UNUSED(moduleName)
UNUSED(pythonHome)

if (strcmp(functionName, "external_library_function")) {
return "Argument functionName must be \"external_library_function\".";
Expand Down Expand Up @@ -62,10 +61,9 @@ class ExternalLibraryObject {

};

void* createExternalObject(const char *filename, const char *moduleName, const char *className, const char *pythonHome, const ModelicaUtilityFunctions_t *callbacks) {
void* createExternalObject(const char *filename, const char *moduleName, const char *className, const ModelicaUtilityFunctions_t *callbacks) {

UNUSED(moduleName)
UNUSED(pythonHome)

s_callbacks = *callbacks;

Expand Down
41 changes: 2 additions & 39 deletions C/src/ExternalLibraryPython.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,7 @@

#define PYTHON_MODULE "external_library"

const char* externalFunction(const char* filename, const char* moduleName, const char* functionName, const char* pythonHome, int nu, const double u[], int ny, double y[]) {

#ifdef _WIN32
DWORD dwAttrib = GetFileAttributesA(pythonHome);

int valid = dwAttrib != INVALID_FILE_ATTRIBUTES;
int isdir = dwAttrib & FILE_ATTRIBUTE_DIRECTORY;

if (!pythonHome || !valid || !isdir) {
return "Argument pythonHome must be a valid directory.";
}

BOOL res = SetDllDirectoryA(pythonHome);
#endif

size_t size;
const wchar_t *python_home_w = Py_DecodeLocale(pythonHome, &size);

Py_SetPythonHome(python_home_w);
const char* externalFunction(const char* filename, const char* moduleName, const char* functionName, int nu, const double u[], int ny, double y[]) {

Py_Initialize();

Expand Down Expand Up @@ -92,32 +74,13 @@ typedef struct {
} PythonObjects;


void* createExternalObject(const char* filename, const char* moduleName, const char* className, const char* pythonHome, const ModelicaUtilityFunctions_t* callbacks) {
void* createExternalObject(const char* filename, const char* moduleName, const char* className, const ModelicaUtilityFunctions_t* callbacks) {

if (!filename) {
callbacks->ModelicaError("Argument filename must not be NULL.");
return NULL;
}

#ifdef _WIN32
DWORD dwAttrib = GetFileAttributesA(pythonHome);

int valid = dwAttrib != INVALID_FILE_ATTRIBUTES;
int isdir = dwAttrib & FILE_ATTRIBUTE_DIRECTORY;

if (!pythonHome || !valid || !isdir) {
callbacks->ModelicaError("Argument pythonHome must be a valid directory.");
return NULL;
}

BOOL res = SetDllDirectoryA(pythonHome);
#endif

size_t size;
const wchar_t *python_home_w = Py_DecodeLocale(pythonHome, &size);

Py_SetPythonHome(python_home_w);

Py_Initialize();

PythonObjects *o = malloc(sizeof(PythonObjects));
Expand Down
6 changes: 3 additions & 3 deletions C/tests/ExternalLibrary_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ TEST_CASE("External functions can be loaded and called", "[ExternalLibrary]") {
double u[] = { 1, 2 };
double y[] = { 0, 0 };

auto message = fp_externalFunction(DATA_FILE, MODULE_NAME, FUNCTION_NAME, PYTHON_HOME, 2, u, 2, y);
auto message = fp_externalFunction(DATA_FILE, MODULE_NAME, FUNCTION_NAME, 2, u, 2, y);

CHECK_THAT(message, Equals(""));
CHECK(y[0] == 4);
Expand All @@ -89,7 +89,7 @@ TEST_CASE("External functions can be loaded and called", "[ExternalLibrary]") {

auto fp_createExternalObject = get<createExternalObject>(l, "createExternalObject");

fp_createExternalObject(nullptr, nullptr, nullptr, nullptr, &callbacks);
fp_createExternalObject(nullptr, nullptr, nullptr, &callbacks);

CHECK_THAT(s_errorMessage, Equals("Argument filename must not be NULL."));
}
Expand All @@ -103,7 +103,7 @@ TEST_CASE("External functions can be loaded and called", "[ExternalLibrary]") {
double u[] = { 1, 2 };
double y[] = { 0, 0 };

void *externalObject = fp_createExternalObject(DATA_FILE, MODULE_NAME, CLASS_NAME, PYTHON_HOME, &callbacks);
void *externalObject = fp_createExternalObject(DATA_FILE, MODULE_NAME, CLASS_NAME, &callbacks);

REQUIRE(externalObject);

Expand Down
1 change: 0 additions & 1 deletion ExternalLibrary/ExternalLibraryFunctionExample.mo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ model ExternalLibraryFunctionExample
Modelica.Utilities.Files.loadResource("modelica://ExternalLibrary/Resources/Data/data.txt"),
"external_library",
"external_library_function",
Modelica.Utilities.System.getEnvironmentVariable("PYTHONHOME"),
{1,2});

end ExternalLibraryFunctionExample;
3 changes: 1 addition & 2 deletions ExternalLibrary/ExternalLibraryObject.mo
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ model ExternalLibraryObject
parameter String filename "Filename of the data file";
parameter String moduleName "Python module name of the external object";
parameter String className "Class name of the external object";
parameter String pythonHome "Path to the Python environment";

ExternalLibrary.Internal.ExternalLibraryObject externalObject=
ExternalLibrary.Internal.ExternalLibraryObject(filename, moduleName, className, pythonHome);
ExternalLibrary.Internal.ExternalLibraryObject(filename, moduleName, className);

protected
function evaluate
Expand Down
9 changes: 4 additions & 5 deletions ExternalLibrary/ExternalLibraryObjectExample.mo
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ model ExternalLibraryObjectExample
nout=2,
filename=Modelica.Utilities.Files.loadResource(
"modelica://ExternalLibrary/Resources/Data/data.txt"),
moduleName="external_library", className="ExternalLibraryObject",
pythonHome=Modelica.Utilities.System.getEnvironmentVariable("PYTHONHOME"))
moduleName="external_library", className="ExternalLibraryObject")
annotation (Placement(transformation(extent={{-10,-10},{10,10}})));
equation
connect(sine.y, libraryObject.u[1]) annotation (Line(points={{-39,28},{-26,28},
{-26,-1},{-12,-1}},
{-26,-0.5},{-12,-0.5}},
color={0,0,127}));
connect(sine1.y, libraryObject.u[2]) annotation (Line(points={{-39,-30},{-26,
-30},{-26,1},{-12,1}}, color={0,0,127}));
connect(sine1.y, libraryObject.u[2]) annotation (Line(points={{-39,-30},{-26,-30},
{-26,0.5},{-12,0.5}}, color={0,0,127}));
annotation (
Icon(coordinateSystem(preserveAspectRatio=false)),
Diagram(coordinateSystem(preserveAspectRatio=false, extent={{-100,-100},{
Expand Down
4 changes: 0 additions & 4 deletions ExternalLibrary/Internal.mo
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,12 @@ package Internal
input String filename;
input String moduleName;
input String className;
input String pythonHome;
input ExternalLibrary.Internal.ModelicaUtilityFunctions callbacks=ExternalLibrary.Internal.ModelicaUtilityFunctions();
output ExternalLibraryObject externalObject;
external"C" externalObject = createExternalObject(
filename,
moduleName,
className,
pythonHome,
callbacks)
annotation (Library="ExternalLibrary");
end constructor;
Expand All @@ -68,7 +66,6 @@ package Internal
input String filename;
input String moduleName;
input String functionName;
input String pythonHome;
input Integer nu;
input Real[nu] u;
input Integer ny;
Expand All @@ -78,7 +75,6 @@ package Internal
filename,
moduleName,
functionName,
pythonHome,
size(u, 1),
u,
size(y, 1),
Expand Down
2 changes: 0 additions & 2 deletions ExternalLibrary/externalLibraryFunction.mo
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ function externalLibraryFunction
input String filename;
input String moduleName;
input String functionName;
input String pythonHome;
input Real[2] u={1,2};
output Real[2] y;
protected
Expand All @@ -13,7 +12,6 @@ algorithm
filename,
moduleName,
functionName,
pythonHome,
size(u, 1),
u,
size(y, 1));
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 2-Clause License

Copyright (c) 2023, Dassault Systèmes. All rights reserved.
Copyright (c) 2024, Dassault Systèmes. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Expand Down
25 changes: 18 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,34 @@ To run the tests, right click on the `ExternalLibraryTest` project, select `Set

To build a shared library that calls Python follow the steps above, but after you pressed `Configure`, select `EXTERNAL_LANGUAGE=Python` and set `PYTHON_HOME=C:/Anaconda3` (assuming that you installed Anaconda Python 3.7 in `C:/Anaconda3`).

To install the Python library in development mode run `pip install -e Python` in the root of the repository. This will allow you edit the Python code without reinstalling the library.
To install the Python library in development mode run `pip install -e Python` in the root of the repository.
This will allow you edit the Python code without reinstalling the library.

Run `ExternalLibraryTest` in Visual Studio to check if everything is set up correctly.
In order use load the Python binaries and their dependencies two environment variables need to be for the process loading the `ExternalLibrary.dll`.

`PYTHONHOME` needs to point to the folder that contains the Python executable.
It can retrieved by running `which python`.

`PATH` needs to contain all folders that contain Python binaries and dependencies.
For Conda environments these can be retrived by running `set PATH` inside a Conda prompt.

On the `ExternalLibraryTest` project in Visual Studio right-click and selct `Properties > Configuration Properties > Debugging`.
In `Environment` set the two environment variables and run the project to check if everything is set up correctly.

## Modelica library

The Modelica library is the same for both the C++ and the Python backend. It contains a Modelica function `libraryFunction` that can be used e.g. to load data at the beginning of a simulation and an ExternalObject `LibraryObject` to connect to external code during the simulation (e.g. to inter- or extrapolate using special algorithms).
The Modelica library is the same for both the C++ and the Python backend.
It contains a Modelica function `libraryFunction` that can be used e.g. to load data at the beginning of a simulation and an ExternalObject `LibraryObject` to connect to external code during the simulation (e.g. to inter- or extrapolate using special algorithms).

![Modelica library](ExternalLibrary/Resources/Images/ExternalLibrary.png)

After loading the `ExternalLibrary` package in Dymola, go to `Commands` and run `Advanced.CompileWith64=2` to compile 64-bit binaries and `Modelica.Utilities.System.setEnvironmentVariable("PYTHONHOME", "C:/Anaconda3")`, so the shared library knows where to search for the Python installation.
After loading the `ExternalLibrary` package in Dymola, go to `Commands` and run `Advanced.CompileWith64=2` to compile 64-bit binaries and `Modelica.Utilities.System.setEnvironmentVariable("PYTHONHOME", ...)` and `Modelica.Utilities.System.setEnvironmentVariable("PATH", ...)` (see previous section), so the shared library knows where to search for the Python installation and dependencies.

To run the function with the default parameters, right click `libraryFunction` and select `Run Function...` and then `Execute`.
To run the function with the default parameters, right click `externalLibraryFunction` and select `Run Function...` and then `Execute`.

The `LibraryObjectExample` demonstrates the use of the external object.
The `ExternalLibraryFunctionExample` and `ExternalLibraryObjectExample` demonstrates the use of the external function and object respectively.

## License

Copyright &copy; 2023 Dassault Syst&egrave;mes.
Copyright &copy; 2024 Dassault Syst&egrave;mes.
The code is released under the [2-Clause BSD license](LICENSE.txt).

0 comments on commit 1f6466e

Please sign in to comment.