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
1 change: 1 addition & 0 deletions RationaleMCP/0031/ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ These are subtopics that are considered necessary to resolve for a first version
- [x] Remove byte order mark, as it is already deprecated in full Modelica. [PR with discussion](https://github.com/modelica/ModelicaSpecification/pull/3528)
- [ ] Base Modelica package shall have no dependencies on other loaded classes.
- [ ] Management of resources and Modelica URIs.
- [ ] External function interface and external objects.

### Base Modelica 0.1+…1.0 (future MCPs)
In future minor versions of Base Modelica 1, we could improve the language by incorporating smaller improvements that were not considered necessary for version 1.0.
Expand Down
43 changes: 41 additions & 2 deletions RationaleMCP/0031/differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ This change was made to support the [changed definitions of _constant expression

Base Modelica functions cannot have function default arguments.
A tool producing Base Modelica from full Modelica can accommodate this by automatically generating a helper function for every present subset of arguments used in the model.
(The helpers can be omitted if the defaults are literal or otherwise independent of the other inputs, since those values can be added in each call.
(The helpers can be omitted if the defaults are literal or otherwise independent of the other inputs, since those values can be added in each call.
No helper function needs to be created for argument combinations that aren't used in the model, which means that the potential combinatorial explosion of possible argument combinations is avoided.)

For example, consider this full Modelica model:
Expand Down Expand Up @@ -843,7 +843,7 @@ model A
Real x(start = 1.0);
Real y;
Real z;
equation
equation
y = 5 * x;
z = 7 * x;
x + y + z = sin(time + x + y + z);
Expand Down Expand Up @@ -1304,6 +1304,45 @@ Accordingly, a function component declaration which is neither input nor output
The new annotation `protected = true` provides a standardized way to indicate that a component declaration in Base Modelica comes from a protected section in the full Modelica model.
See [`protected` annotation](annotations.md#protected).

## External Functions

Base Modelica restricts the full Modelica external function interface ([12.9](https://specification.modelica.org/master/functions.html#external-function-interface)) in several ways.

**Language.**
Only `"C"` is permitted as the language specification; `"FORTRAN 77"` is dropped.
See [grammar](grammar.md#B22-Class-definition) for the restricted `_language-specification_` rule.

**Annotations.**
The `Include`, `IncludeDirectory`, and `SourceDirectory` annotations are not allowed in Base Modelica.
Only binary distribution is supported, so the permitted annotations on the `external` clause are `Library`, `LibraryDirectory`, and `License`.
Both `Library` and `LibraryDirectory` must always be given explicitly — no defaults are provided.
See [grammar](grammar.md#B22-Class-definition) for the `_external-annotation_` rule.

**URI scheme.**
The `modelica://` URI scheme is replaced by `base-modelica:/`, which is always relative to the directory of the current `.bmo` file.
This removes any dependency on package path lookups.

**Library layout.**
The platform (e.g. `linux64`, `win64`) must be given as a mandatory subdirectory of the `LibraryDirectory` path.
Placing libraries directly in `LibraryDirectory` is not allowed.
Using the model name as part of the path (e.g. `base-modelica:/Example/PackageA/Library`) is recommended to prevent naming conflicts when two packages provide a library with the same filename.

**External objects.**
The `class` keyword is the only class prefix allowed Base Modelica to support external objects that extend `ExternalObject`.
The `constructor` and `destructor` functions follow the same rules as in full Modelica ([12.9.8](https://specification.modelica.org/master/functions.html#external-objects)); no further changes are needed.

### Rationale

The introduction of the new URI scheme `base-modelica:/` removes the ambiguity of having a class that extends `ExternalObject`.
With the need to explicitly state what the name of the library is and where the library is located, it is completely clear where to look for the external function during linkage.
No guesswork about `MODELICAPATH` lookups or conflicting library versions is needed.

This simplifies the work a Base Modelica tool has to perform.
For example, a source code library needs to be compiled for the target architecture of the host system before a simulation executable can link to it.
Consequently, this removes the need to compile arbitrary C files during transformation from Base Modelica to a simulation executable.

All compilation of source code has to be handled by the tool lowering Modelica to Base Modelica or the modeler.
The restricted set of Modelica annotations is the simplest way to build a simulation executable from Base Modelica.

## Clock partitions

Expand Down
36 changes: 36 additions & 0 deletions RationaleMCP/0031/examples/BaseModelica/Example.bmo
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! base 0.1.0
package 'Example'
function 'PackageA.ExternalFunc1' "Include header file for library implementation"
input Real 'x';
output Real 'y';
external "C" 'y' = ExternalFunc1_ext('x') annotation(
Library = "ExternalLib1",
LibraryDirectory = "base-modelica:/Example/PackageA/Library");
end 'PackageA.ExternalFunc1';

function 'PackageA.ExternalFunc2' "Include header file for library implementation"
input Real 'x';
output Real 'y';
external "C" annotation(
Library = "ExternalLib2",
LibraryDirectory = "base-modelica:/Example/PackageA/Library");
end 'PackageA.ExternalFunc2';

function 'PackageB.ExternalFunc1' "Source file was compiled into a library by lowering tool"
input Real 'x';
output Real 'y';
external "C" annotation(
Library = "ExternalLib1",
LibraryDirectory = "base-modelica:/Example/PackageB/Library");
end 'PackageB.ExternalFunc1';

model 'Example'
Real 'x'(start = 1.0);
Real 'y'(start = 2.0);
Real 'z'(start = 3.0);
equation
der('x') = -'PackageA.ExternalFunc1'('x');
der('y') = -'PackageA.ExternalFunc2'('y');
der('z') = -'PackageB.ExternalFunc1'('z');
end 'Example';
end 'Example';
Binary file not shown.
Binary file not shown.
Binary file not shown.
40 changes: 40 additions & 0 deletions RationaleMCP/0031/examples/BaseModelica/ExternalObjectExample.bmo
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//! base 0.1.0
package 'ExternalObjectExample'
function 'Multiplication.multiply' "Multiply x by the factor stored in the external object"
input 'Multiplication.Multiplier' 'm';
input Real 'x';
output Real 'y';
external "C" 'y' = Multiplier_multiply('m', 'x') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end 'Multiplication.multiply';

class 'Multiplication.Multiplier'
extends ExternalObject;

function constructor "Allocate and initialise the multiplier"
input Real 'factor';
output 'Multiplication.Multiplier' 'obj';
external "C" 'obj' = Multiplier_constructor('factor') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end constructor;

function destructor "Free the multiplier"
input 'Multiplication.Multiplier' 'obj';
external "C" Multiplier_destructor('obj') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end destructor;

end 'Multiplication.Multiplier';

model 'ExternalObjectExample' "Multiply a decaying signal by a constant factor stored in an external object"
'Multiplication.Multiplier' 'm' = 'Multiplication.Multiplier'(3.0);
Real 'x'(start = 1.0) "Decaying signal";
Real 'y' "Scaled output: y = 3 * x";
equation
der('x') = -'x';
'y' = 'Multiplication.multiply'('m', 'x');
end 'ExternalObjectExample';
end 'ExternalObjectExample';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.a
*.so
*.dylib
*.lib
*.dll
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! base 0.1.0
package 'ExternalObjectExample'
function 'Multiplication.multiply' "Multiply x by the factor stored in the external object"
input 'Multiplication.Multiplier' 'm';
input Real 'x';
output Real 'y';
external "C" 'y' = Multiplier_multiply('m', 'x') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end 'Multiplication.multiply';

function 'Multiplication.Multiplier.constructor' "Allocate and initialise the multiplier"
input Real 'factor';
output 'Multiplication.Multiplier' 'obj';
external "C" 'obj' = Multiplier_constructor('factor') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end 'Multiplication.Multiplier.constructor';

function 'Multiplication.Multiplier.destructor' "Free the multiplier"
input 'Multiplication.Multiplier' 'obj';
external "C" Multiplier_destructor('obj') annotation(
Library = "Multiplication",
LibraryDirectory = "base-modelica:/ExternalObjectExample/Multiplication/Library");
end 'Multiplication.Multiplier.destructor';

model 'ExternalObjectExample' "Multiply a decaying signal by a constant factor stored in an external object"
'Multiplication.Multiplier' 'm' = 'Multiplication.Multiplier.constructor'(3.0);
Real 'x'(start = 1.0) "Decaying signal";
Real 'y' "Scaled output: y = 3 * x";
equation
der('x') = -'x';
'y' = 'Multiplication.multiply'('m', 'x');

when terminate then // New construct
'Multiplication.Multiplier.destructor'('m');
end when;
end 'ExternalObjectExample';
end 'ExternalObjectExample';
9 changes: 9 additions & 0 deletions RationaleMCP/0031/examples/Modelica/Example.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
model Example
Real x(start = 1.0);
Real y(start = 2.0);
Real z(start = 3.0);
equation
der(x) = -PackageA.ExternalFunc1(x);
der(y) = -PackageA.ExternalFunc2(y);
der(z) = -PackageB.ExternalFunc1(z);
end Example;
11 changes: 11 additions & 0 deletions RationaleMCP/0031/examples/Modelica/ExternalObjectExample.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
model ExternalObjectExample "Multiply a decaying signal by a constant factor stored in an external object"
// The external object is created once with factor = 3.0.
// It lives on the C heap for the duration of the simulation.
Multiplication.Multiplier m = Multiplication.Multiplier(factor = 3.0);

Real x(start = 1.0) "Decaying signal";
Real y "Scaled output: y = 3 * x";
equation
der(x) = -x;
y = Multiplication.multiply(m, x);
end ExternalObjectExample;
3 changes: 3 additions & 0 deletions RationaleMCP/0031/examples/Modelica/Multiplication/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Resources/Library/linux64/
Resources/Library/win64/
Resources/Source/build-*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* Multiplier.h – opaque external object storing a multiplication factor */
#ifndef MULTIPLIER_H
#define MULTIPLIER_H

void *Multiplier_constructor(double factor);
void Multiplier_destructor(void *obj);
double Multiplier_multiply(void *obj, double x);

#endif /* MULTIPLIER_H */
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.15)
project(Multiplication C)

# Supported targets (set via toolchain file or detected automatically):
# linux64 – 64-bit Linux (native: -DCMAKE_TOOLCHAIN_FILE=toolchains/linux64.cmake)
# win64 – 64-bit Windows (cross: -DCMAKE_TOOLCHAIN_FILE=toolchains/win64.cmake)
#
# Usage:
# Native Linux 64-bit build:
# cmake -B build-linux64 -DCMAKE_TOOLCHAIN_FILE=toolchains/linux64.cmake
# cmake --build build-linux64
#
# Cross-compile to win64 (requires mingw-w64):
# cmake -B build-win64 -DCMAKE_TOOLCHAIN_FILE=toolchains/win64.cmake
# cmake --build build-win64

if(NOT DEFINED MODELICA_PLATFORM)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(MODELICA_PLATFORM "win64")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(MODELICA_PLATFORM "linux64")
else()
set(MODELICA_PLATFORM "unknown")
endif()
endif()

message(STATUS "Modelica platform tag: ${MODELICA_PLATFORM}")

set(LIBRARY_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../Library/${MODELICA_PLATFORM}")

# ── Multiplication (static) ──────────────────────────────────────────────────

add_library(Multiplication STATIC Multiplier.c)
target_include_directories(Multiplication PRIVATE ../Include)

set_target_properties(Multiplication PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${LIBRARY_OUTPUT_DIR}"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Multiplier.c – external object that stores and applies a multiplication factor */
#include <stdlib.h>
#include "Multiplier.h"

typedef struct {
double factor;
} Multiplier;

void *Multiplier_constructor(double factor) {
Multiplier *m = (Multiplier *)malloc(sizeof(Multiplier));
m->factor = factor;
return (void *)m;
}

void Multiplier_destructor(void *obj) {
free(obj);
}

double Multiplier_multiply(void *obj, double x) {
Multiplier *m = (Multiplier *)obj;
return m->factor * x;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Toolchain: Linux 64-bit
# Native build — no extra packages required on a 64-bit Linux host.
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(MODELICA_PLATFORM "linux64" CACHE STRING "" FORCE)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Toolchain: Windows 64-bit via MinGW-w64
# Requires: mingw-w64 (e.g. apt install mingw-w64)
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)

set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

set(MODELICA_PLATFORM "win64" CACHE STRING "" FORCE)
41 changes: 41 additions & 0 deletions RationaleMCP/0031/examples/Modelica/Multiplication/package.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package Multiplication

class Multiplier "External object storing a multiplication factor"
extends ExternalObject;

function constructor "Allocate and initialise the multiplier"
input Real factor;
output Multiplier obj;
external "C"
obj = Multiplier_constructor(factor)
annotation(
Library = "Multiplication",
Include = "#include \"Multiplier.h\"",
LibraryDirectory = "modelica://Multiplication/Resources/Library");
end constructor;

function destructor "Free the multiplier"
input Multiplier obj;
external "C"
Multiplier_destructor(obj)
annotation(
Library = "Multiplication",
Include = "#include \"Multiplier.h\"",
LibraryDirectory = "modelica://Multiplication/Resources/Library");
end destructor;

end Multiplier;

function multiply "Multiply x by the factor stored in the external object"
input Multiplier m;
input Real x;
output Real y;
external "C"
y = Multiplier_multiply(m, x)
annotation(
Library = "Multiplication",
Include = "#include \"Multiplier.h\"",
LibraryDirectory = "modelica://Multiplication/Resources/Library");
end multiply;

end Multiplication;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// File ExternalFunc1.h
#ifndef EXTERNALFUNC1_H
#define EXTERNALFUNC1_H

double ExternalFunc1_ext(double x);

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// File ExternalFunc2.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _MSC_VER
#ifdef EXTERNAL_FUNCTION_EXPORT
# define EXTLIB2_EXPORT __declspec( dllexport )
#else
# define EXTLIB2_EXPORT __declspec( dllimport )
#endif
#elif __GNUC__ >= 4
/* In gnuc, all symbols are by default exported. It is still often useful,
to not export all symbols but only the needed ones */
# define EXTLIB2_EXPORT __attribute__ ((visibility("default")))
#else
# define EXTLIB2_EXPORT
#endif

EXTLIB2_EXPORT double ExternalFunc2(double x);

#ifdef __cplusplus
}
#endif
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# CMake build directories
build-*/
Loading