To develop an integration model or an substructure model in Python for communication, the developer needs to provide the explicit linkage to the DataExchange DLL in the code. This document assumes that the develoepr is familiar with the Python language.
Link with DataExchange library (Python)¶
To explicitly link with the DLL in Python, the DataExchange.dll should be first loaded to Python using the following command.
from ctypes import *
# load DLL into memory
dll = cdll.LoadLibrary(".\DataExchange.dll")
Next, the python function names for the DataExhcange.dll functions should be defined as follows:
setupconnection = dll.setupconnection
setupconnection.argtypes = [c_int, POINTER(c_int), c_int, POINTER(c_char), c_int]
initialization = dll.initialization
initialization.argtypes = [c_int, c_int, c_int]
command = dll.command
command.argtypes = [c_int, c_int, c_int]
recvdata = dll.recvdata
recvdata.argtypes = [c_int, POINTER(c_double), c_int, c_int]
senddata = dll.senddata
senddata.argtypes = [c_int, POINTER(c_double), c_int, c_int]
close = dll.close
close.argtypes = [POINTER(c_int)]
updatemessageheader = dll.updatemessageheader
updatecommand = dll.updatecommand
updatenumstep = dll.updatenumstep
updatedatatype = dll.updatedatatype
indicator = dll.indicator
Now the above python functions hold the functions of the DLL and you can call them directly in the following. For example, the way to use the setupconnection()
function in the DataExchange.dll to establish the connection is shown below
iResult = setupconnection(PortNumber, &sockfd, flag, machineInetAddr, Constant.TCP_IP);
Integration Model (Python)¶
In addition to the above code to link with the DataExchange.dll, the integration model starts with declaring variables required for communication.
# define socket variables
PortNumber = 8090 # Port number
machineInetAddr = (c_char * 100)(*bytes("127.0.0.1", 'utf-8')) # IP address
cast(machineInetAddr, POINTER(c_char))
sockfd = 0 # variable for new created socket id
flag = 1 # 1 - integration module; 2 - substructure module
numdofs = 3 # Total interface DOFs
where
- PortNumber: port number. This number should be consistent with the port number specified in the substructure model below.
- machineInetAddr - IP address of machine with the substructure model. For the case that the integration and substructure models are modelled in the same Windows machine, the standard IP address of "127.0.0.1" can be used.
- sockfd: a SOCKET type variable for a newly generated socket
- flag: a variable to be used in the DataExchange.dll functions to indicate the model is a integration model or a substructure model.
- numdofs: a variable defines the total number of degrees of freedom at the interface nodes
Communication Initialization¶
Then, three DLL functions are called to initialize the data exchange format, set up the connection with the substructure model, and send the initialized data exchange format to the substructure model.
# Initialize data exchange format
updatemessageheader(2, 0, Constant.Software_only, 1, Constant.Double_precision, numdofs)
# setup connection
sockfdPtr = (c_int * 100)(*[0])
cast(sockfdPtr, POINTER(c_int))
iResult = setupconnection(PortNumber, sockfdPtr, flag, machineInetAddr, Constant.TCP_IP)
if iResult != 0:
print('Connection failed.')
else:
print('Connection done')
# send data exchange format to server for initialization
iResult = initialization(sockfdPtr[0], flag, Constant.TCP_IP)
if iResult != 0:
print('Initialization failed.')
else:
print('Initialization done')
where updatemessageheader(version, command, testtype, subtype, precision, numdofs) function is used to initialize the following information defined in the header block of the data exchanged through UTNP (see here for more details).
- version: this parameter corresponds to the Version parameter in the data exchange format. It is 2 for the current release version.
- command: this parameter corresponds to the Command parameter in the data exchange format. It can be defined as zero at this initialization stage.
- testtype: this parameter corresponds to thie Test type parameter in the data exchange format. Depending on the simulation methods, one of the following values can be used to initialize the parameter.
- 1 - Pseudo-dynamic (ramp-hold) simulation
- 2 - Pseudo-dynamic (continuous) simulation
- 4 - Numerical multi-platform simulation
- subtype: this parameter corresponds to the Substructure type parameter in the data exchange format. Depending on the substructure modules, one of the following values can be used to initialize the parameter
- 1 - OpenSees
- 2 - Zeus-NL
- 3 - ABAQUS
- 4 - VecTor2
- 5 - NICON-AIO or NICON-NIO
- 6 - VecTor4
- 7 - LS-Dyna
- precision: this parameter corresponds to the Precision parameter in the data exchange format. It can be defined as 1 for single precision and 2 for double precision
- numdofs: this parameter corresponds to the Number of DOFs parameter in the data exchange format. It is equal to the total number of effective DOFs at the interface nodes for communication.
Sending Data¶
Once communication is established and the data format parameters are initialized, the integration module starts with sending displacement command to the substructure.
#------------------------------------------------------------------------------------
# send trial displacement to substructure module
#------------------------------------------------------------------------------------
# update the command in the message header block for imposing target displacement
updatecommand(Constant.Impose_TargetValues)
# define the type of data to be appended to the message header
# updatesubtype (disp, vel, accel, force, stiff, mass,temperature)
# use 1 and 0 to enable and disable the data in the function.
updatedatatype(1, 0, 0, 0, 0, 0, 0)
# update number of step
updatenumstep(i+1)
# send the updated data header to the substructure module
action = command(sockfdPtr[0], flag, Constant.TCP_IP)
# calculate the data size to be appended to the message header
lens = indicator()
# send trial disp to substructure module
sdata = (c_double * lens)(*unn[0:3,i])
cast(sdata, POINTER(c_double))
iResult = senddata(sockfdPtr[0], sdata, lens, Constant.TCP_IP)
if iResult != 0:
print('Failed to send disp.')
else:
print('SendCMD')
Receiving Data¶
Once the restoring force is computed in the substructure module, the integration module sends another command to receive the computed force from the substructure module.
#------------------------------------------------------------------------------------
# receive restoring force from substructure module
#------------------------------------------------------------------------------------
# update the command in the message header for receiving data from the substructure module
updatecommand(Constant.Report_Values)
# define the type of data to be appended to the message header
# updatesubtype (disp, vel, accel, force, stiff, mass,temperature)
# use 1 and 0 to enable and disable the data in the function.
updatedatatype(0, 0, 0, 1, 0, 0, 0)
# send the updated message header to the substructure module
action = command(sockfdPtr[0], flag, Constant.TCP_IP)
# calculate the size to be appended to the message header
lens = indicator()
# receive the computed restoring force from substructure module
feedback = (c_double * lens)(*[0, 0, 0])
cast(feedback, POINTER(c_double))
iResult = recvdata(sockfdPtr[0], feedback, lens, Constant.TCP_IP)
if iResult != 0:
print('Failed to receive force.')
else:
print('RecvCMD')
The above sending and receving data steps are repeated until the end of the simulation.
Disconnection¶
Once the simulation is done, the following is included in the code to disconnect the integration module from the substructure module.
#------------------------------------------------------------------------------------
# terminate communication
#------------------------------------------------------------------------------------
# update the command in the message header
updatecommand(Constant.Terminate)
# send the updated message header to the substructure module
action = command(sockfdPtr[0], flag, Constant.TCP_IP)
# disconnect and close socket
iResult = close(sockfdPtr)
print('Simulation done.')
Substructure Model (C/C++)¶
Similar to the above integration module, the variables for port number, IP address, socket number, and indicator of module type should be defined after the program is linked with the DataExchange.dll.
# define socket variables
PortNumber = 8090 # Port number
machineInetAddr = (c_char * 100)(*bytes("0.0.0.0", 'utf-8')) # IP address
cast(machineInetAddr, POINTER(c_char))
sockfd = 0 # variable for new created socket id
flag = 2 # 1 - integration module; 2 - substructure module
Communication Initialization¶
The same updatemessageheader, setupconnection, initialization are used in the substructure module to initialize the message header and connect with the integration module. The only difference is the value of flag which indicates that the dll functions are being called by a substructure module.
# Initialize data exchange format
updatemessageheader(2, 0, 0, 1, Constant.Double_precision, 0)
# setup connection
sockfdPtr = (c_int * 100)(*[0])
cast(sockfdPtr, POINTER(c_int))
iResult = setupconnection(PortNumber, sockfdPtr, flag, machineInetAddr, Constant.TCP_IP)
if iResult != 0:
print('Connection failed.')
else:
print('Connection done')
# send data exchange format to server for initialization
iResult = initialization(sockfdPtr[0], flag, Constant.TCP_IP)
if iResult != 0:
print('Initialization failed.')
else:
print('Initialization done')
After the connection is built, a switch statement is included, which defines different actions based on the received command values in the message header.
# receive data format from integration module
action = command(sockfdPtr[0], flag, Constant.TCP_IP)
match action:
case Constant.Impose_TargetValues:
# code to receive data from the integration module (see below)
case Constant.Report_Values:
#code to send computed data to the integration module (see below)
case Constant.Terminate:
# exit the switch statement
exitFlag = 0
case _:
print('Invalid action received')
Receiving Data¶
In the above case of Constant.Impose_TargetValues, the substructure module receives the displacement from the integration module. Detailed code is shown below:
# calculate the size to be appended to the message header
lens = indicator()
print(lens)
rdata = (c_double * lens)(*[0,0,0])
cast(rdata, POINTER(c_double))
# receive displacement from the integration module
iResult = recvdata(sockfdPtr[0], rdata, lens, Constant.TCP_IP)
if iResult != 0:
print('Failed to receive trial displacement.')
else:
print('Trial displacement received.')
# save displacement for analysis
Tdisp = rdata[0:3]
Sending Data¶
In the case of Constant.Report_Values, the substructure module sends the computed restoring force back to the integration module. Detailed code is shown below:
# calculate the size to be appended to the message header
lens = indicator();
# calculate restoring force
RF = np.zeros((3,1))
RF = np.matmul(K,Tdisp[:])
sdata = (c_double * lens)(*RF[0:3])
cast(sdata, POINTER(c_double))
# send force to integration module
iResult = senddata(sockfdPtr[0], sdata, lens, Constant.TCP_IP)
if iResult != 0:
print('Failed to send computed force.')
else:
print('Computed force sent.')
Disconnection¶
At the end of the simulation, the communication is disconnected.
# disconnect and close socket
iResult = close(sockfdPtr);
print('Simulation done')
Example Download¶
The source code the above communication example in python can be downloaded from here.