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
32 changes: 32 additions & 0 deletions python/transformations/fmu-integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# FMU Integration

This sample shows how to execute an FMU inside a Quix Streams application. It consumes messages from an input topic, runs the FMU for each message, and publishes enriched results to an output topic.


## How to use this sample

- Add a valid `.fmu` file to the app. It should be compiled for Linux if you plan to run it on Quix. The repo includes `simulink_example_inports.fmu` in this folder as an example.
- Edit the filename near the top of `main.py`:
```python
fmu_filename = "simulink_example_inports.fmu"
```

- When the app starts, it prints the FMU model variables with their `valueReference` and `causality` so you know which variables are inputs and outputs.
- Define the FMU_processing function according to your model inputs and outputs.

## Environment variables

The code sample uses the following environment variables:

- **input**: Name of the input topic to listen to.
- **output**: Name of the output topic to write to.

## Contribute

Submit forked projects to the Quix [GitHub](https://github.com/quixio/quix-samples) repo. Any new project that we accept will be attributed to you and you'll receive $200 in Quix credit.

## Open source

This project is open source under the Apache 2.0 license and available in our [GitHub](https://github.com/quixio/quix-samples) repo.

Please star us and mention us on social to show your appreciation.
17 changes: 17 additions & 0 deletions python/transformations/fmu-integration/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: FMU Integration
language: python
variables:
- name: input
inputType: InputTopic
multiline: false
description: Name of the input topic to listen to.
defaultValue: 2d-vector
- name: output
inputType: OutputTopic
multiline: false
description: Name of the output topic to write to.
defaultValue: fmu-output
dockerfile: dockerfile
runEntryPoint: main.py
defaultFile: main.py
libraryItemId: starter-transformation
28 changes: 28 additions & 0 deletions python/transformations/fmu-integration/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM python:3.12.5-slim-bookworm

# Set environment variables for non-interactive setup and unbuffered output
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONIOENCODING=UTF-8 \
PYTHONPATH="/app"

# Build argument for setting the main app path
ARG MAINAPPPATH=.

# Set working directory inside the container
WORKDIR /app

# Copy requirements to leverage Docker cache
COPY "${MAINAPPPATH}/requirements.txt" "${MAINAPPPATH}/requirements.txt"

# Install dependencies without caching
RUN pip install --no-cache-dir -r "${MAINAPPPATH}/requirements.txt"

# Copy entire application into container
COPY . .

# Set working directory to main app path
WORKDIR "/app/${MAINAPPPATH}"

# Define the container's startup command
ENTRYPOINT ["python3", "main.py"]
35 changes: 35 additions & 0 deletions python/transformations/fmu-integration/library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"libraryItemId": "fmu-integration-transformation",
"name": "FMU Integration Transformation",
"language": "Python",
"IsHighlighted": false,
"tags": {
"Complexity": ["Medium"],
"Technology": ["Quix Streams", "FMU"],
"Pipeline Stage": ["Transformation"],
"Type": ["Code samples"],
"Popular Subjects": []
},
"shortDescription": "Consume data from a topic, run an FMU model on it and publish the enriched results to an output topic",
"DefaultFile": "main.py",
"EntryPoint": "dockerfile",
"RunEntryPoint": "main.py",
"Variables": [
{
"Name": "input",
"Type": "EnvironmentVariable",
"InputType": "InputTopic",
"Description": "Name of the input topic to listen to",
"DefaultValue": "2d-vector",
"Required": true
},
{
"Name": "output",
"Type": "EnvironmentVariable",
"InputType": "OutputTopic",
"Description": "Name of the output topic to write to.",
"DefaultValue": "fmu-output",
"Required": true
}
]
}
72 changes: 72 additions & 0 deletions python/transformations/fmu-integration/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from quixstreams import Application
from fmpy import read_model_description, extract, instantiate_fmu, simulate_fmu
import pandas as pd
import numpy as np
import os

# Check FMU model
fmu_filename = "simulink_example_inports.fmu" # adjust if in another path

# 1) read model description (variable names, valueReferences, interface type)
print("FMU MODEL:")
md = read_model_description(fmu_filename)
print('FMI version:', md.fmiVersion)
for v in md.modelVariables:
print(v.name, v.valueReference, v.causality)

# Define matlab function call
def FMU_processing(row: dict):
x = row["x"]
y = row["y"]
theta = np.pi / 4 # 45 degrees in radians

# Build structured input
input_data = np.array(
[(0.0, x, y, theta)],
dtype=[('time', np.float64),
('x', np.float64),
('y', np.float64),
('theta', np.float64)]
)

result = simulate_fmu(
fmu_filename,
start_time=0.0,
stop_time=0.0,
input=input_data
)

result_df = pd.DataFrame.from_records(result)

# Convert to standard Python float for JSON serialization
row["x_new"] = float(result_df["Out1"][0])
row["y_new"] = float(result_df["Out2"][0])


def main():
# Setup necessary objects
app = Application(
consumer_group="FMU-Model-Run",
auto_create_topics=True,
auto_offset_reset="earliest"
)
input_topic = app.topic(name=os.environ["input"])
output_topic = app.topic(name=os.environ["output"])
sdf = app.dataframe(topic=input_topic)


# Do StreamingDataFrame operations/transformations here
#sdf.print_table()
sdf = sdf.update(FMU_processing)
sdf.print_table()

# Finish off by writing to the final result to the output topic
sdf.to_topic(output_topic)

# With our pipeline defined, now run the Application
app.run()


# It is recommended to execute Applications under a conditional main
if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions python/transformations/fmu-integration/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
quixstreams==3.14.1
python-dotenv
pandas
fmpy
numpy
Binary file not shown.
41 changes: 41 additions & 0 deletions python/transformations/matlab-wheel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Matlab Wheel

This code sample demonstrates how to run MATLAB functions in Quix as Python-compatible .whl packages.

## How to build the wheel

### 01 - Your MATLAB function
- Ensure you understand the types and number of inputs and outputs of your function.
- Save your .m function file in the compilation-files folder (like the rot.m example).

### 02 - Compile for Quix
Let's compile the MATLAB function using the Quix compiler:
- Open MATLAB from the **compilation-files** folder.
- Run the `quix_compiler.m` script, replacing the arguments:
```matlab
quix_compiler('function_name', 'py')
This will generate a folder named py containing the Python-compatible code, as well as the .whl package that we’ll deploy to Quix.

<### 03 - Update the .whl in the quix app
Replace the existing .whl file in your Quix app with the new one you just built.
⚠️ If the new filename differs from the previous one, make sure to update the requirements.txt file accordingly.

### 04 - Update main.py
Edit the `matlab_processing` function in `main.py` to accommodate your specific function's input and output variables.
>
## Environment variables

The code sample uses the following environment variables:

- **input**: Name of the input topic to listen to.
- **output**: Name of the output topic to write to.

## Contribute

Submit forked projects to the Quix [GitHub](https://github.com/quixio/quix-samples) repo. Any new project that we accept will be attributed to you and you'll receive $200 in Quix credit.

## Open source

This project is open source under the Apache 2.0 license and available in our [GitHub](https://github.com/quixio/quix-samples) repo.

Please star us and mention us on social to show your appreciation.
16 changes: 16 additions & 0 deletions python/transformations/matlab-wheel/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Matlab Wheel
language: python
variables:
- name: input
inputType: InputTopic
multiline: false
description: Name of the input topic to listen to.
defaultValue: 2d-vector
- name: output
inputType: OutputTopic
multiline: false
description: Name of the output topic to write to.
defaultValue: matlab-output
dockerfile: dockerfile
runEntryPoint: main.py
defaultFile: main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# Script to build wheel package from the out folder

set -e

# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="$SCRIPT_DIR/out"

if [ ! -d "$OUT_DIR" ]; then
echo "Error: 'out' directory not found"
exit 1
fi

# Find Python executable
PYTHON_EXEC=$(command -v python3 || command -v python)

if [ -z "$PYTHON_EXEC" ]; then
echo "Error: Python is not installed or not in PATH."
exit 1
fi

echo "Using Python at: $PYTHON_EXEC"
"$PYTHON_EXEC" --version

echo "Installing build tools..."
"$PYTHON_EXEC" -m pip install --upgrade build wheel

echo "Building wheel..."
cd "$OUT_DIR"
"$PYTHON_EXEC" -m build --wheel

echo "Wheel built successfully in $OUT_DIR/dist"

# Copy the generated wheel file(s) to the root script directory
DIST_DIR="$OUT_DIR/dist"

if [ -d "$DIST_DIR" ]; then
WHEEL_FILES=("$DIST_DIR"/*.whl)
if [ -e "${WHEEL_FILES[0]}" ]; then
echo "Copying wheel file(s) to $SCRIPT_DIR"
cp "$DIST_DIR"/*.whl "$SCRIPT_DIR"/
echo "Wheel file(s) copied successfully."
else
echo "No .whl files found in $DIST_DIR"
fi
else
echo "Directory $DIST_DIR does not exist."
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function quix_compiler(function_name, destination_folder, do_zip)
% quix_compiler Compiles a MATLAB function to Python, builds a wheel, and optionally zips the output.
% Inputs:
% function_name - Name of the MATLAB function file (e.g., 'myfunc')
% destination_folder - Destination folder for the compiled output (e.g., 'build_output')
% do_zip - (Optional) Boolean flag: zip the output folder? Default = true

if nargin < 3
do_zip = true;
end

% Define subfolder for mcc output
out_folder = fullfile(destination_folder, 'out');

% Step 0: Create folders if they don’t exist
if ~exist(out_folder, 'dir')
mkdir(out_folder);
end

% Step 1: Compile using mcc into destination_folder/out
try
fprintf('Compiling %s.m into %s...\n', function_name, out_folder);
mcc('-W', ['python:quixmatlab,' function_name], ...
'-d', out_folder, ...
[function_name, '.m']);
catch ME
error('Compilation failed: %s', ME.message);
end

% Step 2: Copy build_wheel.sh to destination_folder
script_name = 'build_wheel.sh';
if exist(script_name, 'file') == 2
try
copyfile(script_name, destination_folder);
fprintf('Copied %s to %s.\n', script_name, destination_folder);
catch ME
error('Failed to copy script: %s', ME.message);
end
else
warning('%s not found in current directory.\n', script_name);
end

% === Step 3: Check if pip is installed, if not, install it ===
[pip_status, ~] = system('python3 -m pip --version');
if pip_status ~= 0
fprintf('pip not found. Installing with get-pip.py...\n');
system('curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py');
system('python3 get-pip.py');
delete('get-pip.py'); % Clean up
else
fprintf('pip is already installed.\n');
end

% === Step 4: Run build_wheel.sh in destination_folder ===
fprintf('Running build_wheel.sh in %s...\n', destination_folder);
build_cmd = sprintf('cd "%s" && bash build_wheel.sh', destination_folder);
build_status = system(build_cmd);
if build_status ~= 0
error('build_wheel.sh failed to run correctly.');
end

% Step 5: Optionally zip the destination_folder
if do_zip
zip_name = [destination_folder, '.zip'];
try
zip(zip_name, destination_folder);
fprintf('Created zip file: %s\n', zip_name);
catch ME
error('Failed to create zip: %s', ME.message);
end
else
fprintf('Skipping zipping step as requested.\n');
end

fprintf('All tasks completed successfully.\n');
end
4 changes: 4 additions & 0 deletions python/transformations/matlab-wheel/compilation-files/rot.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function M = rot(v, theta)
R = [cos(theta) -sin(theta); sin(theta) cos(theta)];
M = R * v;
end
Loading