Skip to content

Migrating python code from HELICS v2.x.x to HELICS v3.x.x

This document will describe the path to migrate your python code from HELICS version 2.x.x to version 3.x.x.

Current pyhelics releases require a HELICS 3 or newer runtime library. Most HELICS 2-era Python aliases have been removed, and PYHELICS_INSTALL can no longer point at a HELICS 2 installation. The following document is a migration reference for updating older code to the HELICS 3 API names and calling conventions.

See this commit for a diff of all the changes.

You can also see the tests folder on github for more examples on how to use the various functions.

See the Pythonic Interface for all the new functionality that is available in pyhelics.

Functions

Related to HelicsMessage

Previously you may have add code to deal with messages like this:

# helics v2
m = h.helicsEndpointGetMessage(endpoint)
assert m.source == "port1"
assert m.original_source == "port1"
assert m.destination == "port2"
assert m.data == len(data)
assert m.time == 2.5

The return object of helicsEndpointGetMessage(endpoint) is now a opaque object and you will have to use helics* functions to get the various attributes of that object.

# helics v3
m = h.helicsEndpointGetMessage(endpoint)
assert h.helicsMessageGetSource(m) == "port1"
assert h.helicsMessageGetOriginalSource(m) == "port1"
assert h.helicsMessageGetDestination(m) == "port2"
assert h.helicsMessageGetByteCount(m) == len(data)
assert h.helicsMessageGetTime(m) == 2.5

Functions named with MessageObject are renamed to use Message. Update old code to use the HELICS 3 names directly.

This function has been renamed: helicsMessageCheckFlag -> helicsMessageGetFlagOption to be consistent with helicsMessageSetFlagOption.

Related to raw data

Additionally, any function that takes bytes as an input must be passed in a python bytestring. If you have a regular python unicode string, as the user yoou are responsible for converting to a byte string with an encoding of your choice.

To convert a python unicode string to a byte string, you can do "hello world".encode(). You can even specify the encoding: "hello world".encode().

Similarly, any helics function that returns a byte string must be handled correctly on the user end. As a user, you are at the moment required to know the encoding of the message in order to convert it to the correct string.

All functions that have Raw in the name are replaced with functions that have Bytes in the name.

e.g.

helicsMessageGetRawData -> helicsMessageGetBytes

Related to complex numbers

Previously, you would need to pass real and imaginary components of complex numbers as separate floating point values.

# helics v2
h.helicsInputSetDefaultComplex(sub, -9.9, 2.5)

Now you can pass them as a complex number.

# helics v3
h.helicsInputSetDefaultComplex(sub, -9.9 + 2.5j)

These are the full list of functions:

  • helicsInputGetComplex
  • helicsPublicationPublishComplex
  • helicsInputSetDefaultComplex

that are affected by this change.

Related to endpoints

The following functions have been renamed and the argument order has been updated:

  • helicsEndpointSendMessageRaw -> helicsEndpointSendBytesTo
  • helicsEndpointSendEventRaw -> helicsEndpointSendBytesToAt

The following functions have been renamed:

  • helicsFederatePendingMessages -> helicsFederatePendingMessageCount
  • helicsEndpointPendingMessages -> helicsEndpointPendingMessageCount

Enumerations

Enumerations are constants now, and are represented by upper case variables:

# helics v2
h.helicsFederateInfoSetIntegerProperty(fedInfo1, h.helics_property_int_log_level, 1)
h.helicsFederateInfoSetIntegerProperty(fedInfo1, h.helics_property_int_max_iterations, 100)
# helics v3
h.helicsFederateInfoSetIntegerProperty(fedInfo1, h.HELICS_PROPERTY_INT_LOG_LEVEL, 1)
h.helicsFederateInfoSetIntegerProperty(fedInfo1, h.HELICS_PROPERTY_INT_MAX_ITERATIONS, 100)

New functionality

If you would like to use callbacks you can do the following in HELICS v3.x.x:

@h.ffi.callback("void logger(HelicsMessage, void* userData)")
def filterFunc1(mess, user_data):
    time = h.helicsMessageGetTime(mess)
    user_data = h.ffi.from_handle(user_data)

    h.helicsMessageSetTime(mess, time + 2.5)
    user_data += 1


class UserData:
    def __init__(self, x):
        self.x = x


...

user_data = UserData(5)
handle = h.ffi.new_handle(user_data)
h.helicsFilterSetCustomCallback(f1, filterFunc1, handle)

...

Find the signature of the function that you’d like to call, and use h.ffi.callback as a python decorator for that function. Current pyhelics releases use the HELICS 3 callback type names, such as HelicsMessage.

Then, you can pass the variable that contains a reference to this C function to a helics callback function. The user is responsible for managing memory with relation to these objects. See cffi’s documentation for more information.