Qt Script Engine

In order to use “any” function for fitting algorithm, the Qt script engine is a very powerful tool. And the basic usage is very simple.

QScriptEngine engine;
QString function = "p1*x + pow(x,2)";
double p1 = 3.2;
double x = 5;
engine.globalObject().setProperty("p1", p1);
engine.globalObject().setProperty("x", x);
ans = engine.evaluate(function_str).toNumber();

The above code will evaluate the expression $p_1 x + x^2$. Although the running time will be a bit slower then hard code, the advantage is that, the expression can be read from a file.

Qt source code for controlling Omron E5CC

These 2 weeks I was fiighting with the manual and try to control the device.

In my application, I need to change the temperature super slowly, not jump from 1 point to the other. I heard that there is an internal function called SP-ramp, that seem to fit my purpose, but I don’t really understand the manual….

The program can control most basic functions, record temperature, set the change rate of SP, limit the output (MV).

However, I don’t know how to get the RUN/STOP and AT (Auto-tune) status, so be careful.

The source code is in Github, feel free to download and modify. The program use Modbus for communication, so, you need to set the device.

https://github.com/TripletESR/Omron_PID

I also made a simplified manual, but I do not post in here for copyright issue. If you want, leave a massage.

Omron E5CC Modbus RTU in QT 101

The Omron E5CC temperature PID controller is using modbus RTU(Remote Terminal Unit) to connect.

First, you prepare a USB-to-RS-485 cable. Connect it to the E5CC. Notice that the A port of the E5CC, should be connected to  negative of the RS-485. Don’t follow the E5CC manual. Also the programing manual is extremely for expert of modbus, not for beginner.

In Modbus, the idea is that, your PC (master) send signal over the connected devices (slaves). The signal is a Hex, contained few things

DeviceID + Function-Code + Address + Command-in-Hex + CRC-16

The master send a signal, only the device with matching DeviceID will respond to the signal. Thus, there is no “locking” that link the master-slave.

The Function-Code is modbus function code, it tell the type of the signal. The type can be reading, writing, diagnostic, etc. This function is part of the modbus protocol, which is common for all devices.

The Address is the memory address stored in the device. The address should be provided by the device manufacture.

Then the command-in-Hex follow.

At the end, a 4-digit Hec CRC-16 is used for error check. Qt can calculate CRC-16 for modbus.

In the Omron E5CC programing manual. Section 4-4-1, we see that the data structure of a read signal. It is read because the Function-Code is 0x03, which is  “Read Holding Registers” in modbus RTU protocal. For example, reading the temperature (PV) of E5CC,

010300 0000 02 C4 0B

0x01 is deviceID, this can be set in the E5CC.

0x03 is the function code

0x0002 is number of value we are going to read. In modbus, one value contains 4-digit Hex, or 0xHHHH. In E5CC, each parameter is stored as 8-digit Hex, or 0xHHHHHHHH. Thus the E5CC manual tell us to get 2 values for temperature reading.

0xC408 is the CRC-16

The device return

01030400 00 03 E8 FA 8D

04 is the number of 8-digit Bin (2-digit Hex = 8-digit Bin). Four of 8-digit Bin = 8-digit Hex.

00 00 03 E8 is the return value of the temperature, converted to DEC is 1000, which is 100.0 ºC.

Run/Stop command

010600 0001 01 49 9A

0x06 is the “Write Single Register” function-Code

0x0101 is the value that start the run/stop for E5CC.

When writing single value in E5CC, only address 0x0000 is allowed. Thus, to write the SV (set value, or SP = Set Point) we have to use write multiple value. (stupid….)

Write multiple value

The set the alarm upper and lower values

011001 0A00 040800 00 03 E8FF FF FC18 8D E9

0x10 is the “Write Multiple Coils”

0x010A is the address of the alarm upper value

0x0004  is the 2 times number of value to be written. In our case, we want to write 2 value, thus the value is 0x4. The address of the next value would be 0x010A + 0x0002 = 0x010C. Since all value use 2 memory slots.

0x08 is the length of the input value. There are 2 value, each value is four 4-digit-Hex, so the input is 0x08.

0x000003E8 is the first value

0xFFFFFC18 is the 2nd value

This structure can also be use to write single value. If we want to change the SV (or Set Point) to 100 ºC. The address is 0x0106.

011001 0600 020400 00 00 64

In Qt, there is an example for modus, call master. We can use it for simple I/O.

Here is a screenshot.

First, we go to Tools to setup the Setting

In the E5CC, you have to set the communication mod to be modbus. In modbus, the parity, data bits, and stop bits are no used.

Since we use USB, this is serial port. The port number can be found using

const auto infos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : infos) {
qDebug("PortName     ="+info.portName())
qDebug() <<"description  =" << (!info.description().isEmpty() ?  info.description() : blankString);
qDebug() <<"manufacturer =" << (!info.manufacturer().isEmpty() ? info.manufacturer() : blankString);
qDebug() <<"serialNumber =" << (!info.serialNumber().isEmpty() ? info.serialNumber() : blankString);
qDebug() <<"Location     =" << info.systemLocation();
qDebug() <<"Vendor       =" << (info.vendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : blankString);
qDebug() <<"Identifier   =  << (info.productIdentifier() ? QString::number(info.productIdentifier(), 16) : blankString);
}

In my case, it is COM4, so I put COM4 in the port. Then connect.

The read the temperature. We set the Table (at the lower left corner) to be Holding Register. In the Read (left panel), Start Address is 0, and Number of values to be 2. In the Qt application output, we will see:

qt.modbus: (RTU client) Sent Serial PDU: 0x0300000002
qt.modbus.lowlevel: (RTU client) Sent Serial ADU: 0x010300000002c40b
qt.modbus: (RTU client) Send successful: 0x0300000002
qt.modbus.lowlevel: (RTU client) Response buffer: “010304”
qt.modbus.lowlevel: (RTU client) Response buffer: “0103040000001cfbfa”

The PDU = Protocol Data Unit, ADU = Accessing Data Unit = DeviceID + PDU + CRC-16. In the last line, the received PDU is 0x03040000001c, the value is 0x0000 and 0x001c=28, which is 18 ºC.

The display in the GUI is:

Now, we try to stop the device.

In the write panel, Start address is 0x0000, and according to the E5CC programing manual, the value consist of two part

AA BB

AA is the command code

BB is the value

From the manual, the command code for RUN/STOP, 0xAA=0x01, and for STOP 0xBB=0x01. Thus, the value is 0x0101. Then we press write.

The Qt application output is

qt.modbus: (RTU client) Sent Serial PDU: 0x0600000101
qt.modbus.lowlevel: (RTU client) Sent Serial ADU: 0x010600000101499a
qt.modbus: (RTU client) Send successful: 0x0600000101
qt.modbus.lowlevel: (RTU client) Response buffer: “01”
qt.modbus: (RTU client) Modbus ADU not complete
qt.modbus.lowlevel: (RTU client) Response buffer: “010600000101499a”

And we can see there is a STOP display on the E5CC.

We can see, although the Holding Register is the same, in read, the function code is 0x03, in write, the function code is 0x06. In Qt manual, the QModBusPdu Class, we can see a list of Function-Code. (https://doc.qt.io/qt-5/qmodbuspdu.html#FunctionCode-enum)

This program is limited 10 address, so the operation is limited. This introduction is the basic, I think user can can study the Qt code to know how to use modbus, how to read and write signal. And people can read this website for another modbus 101.

Qt ActiveX COM

I found that most of the google result is not beginner friendly or not teach-me-as-if-i-am-five enough. Although I am also a beginner, anyway, so. in windows

First, we have a *.dll file which is your ActiveX COM (Component Object Model), we need to register the dll. For convenient, lets say the name of the dll is myAxtiveXCOM.dll. Run command prompt as administrator,

    regsvr32.exe  myActiveXCOM.dll

It will display a massage box telling you the register is successful. Then we need to local the ClassID (CLSID). Open regedit, find myActiveXCOM.dll, then you will find something like

    {BFD7A5AC-CCD3-449C-B99E-4A81DEE2527E}

or the NAME of the dll.

    QT += axcontainer

This is for local COM usage. Then,

    #include <QAxObject>

This enable to use QAxObject, which is an “extension” of QObject that included ActiveX.

    QAxObject *ax = new QAxObject();
ax->setControl("{BFD7A5AC-CCD3-449C-B99E-4A81DEE2527E}");

Then, you can debug, it should be fine and without any error message if the dll is registered correctly.

The activeX COM maker should provide a list of functions. for example, we have

int Connect(string device, int ID)

In QT,

    QVariant ans = ax->dynamicCall("Connect(QString&, int&)", "A", 2);

The dynamicCall is used to call the functions, the function augments has to be converted using Qt variable type. The dynamicCall will return the output. In the above example, Connect will return an integer, so ans will be like

QVariant(int, 1)

Since the dynamicCall can only pass a constant augment to the function of ActiveX COM. When some function is passed by reference, or the function will update the input argument. For example,

void Get_Model_Number(String name)

Then we have to first create a QList<QVariant>

    QList<QVariant> myStr;
myStr << "dummpy";
ax->dynamicCall("Get_Model_Number(QString&)", myStr);


Then, the variable myStr will be update and the value will be changed.

Using TGenPhaseSpace to calculate nuclear reaction

In CERN Root analysis software,  there is a class called TGenPhaseSpace. This class can generate all possible solution of a nuclear reaction ( elastic, inelastic, 2-body, 3-body, decay, etc. ) base on isotropic distribution and balancing four-momenta.

The unit of mass, energy, and momentum are GeV/c^2, GeV, and GeV/c, respectively. The typical usage is

TLorentzVector target;
TLorentzVector beam;

Double_t masses[3] = { m1, m2, m3}; // what particles after reaction

TGenPhaseSpace event;
event.SetDecay(target + beam, 3, masses);

for( Int_t n = 0; n < numEvent; n++){
Double_t xsec = event.Generate();       // cross-section
TLorentzVector *p1 = event.GetDecay(0); // get the 4-momentum of m1
TLorentzVector *p2 = event.GetDecay(1);
TLorentzVector *p3 = event.GetDecay(2);

// fill histogram;
}

In the proton-proton elastic reaction at 300 MeV,  the calculation is very nice.

However, in the 23F(p,2p)22O inverse knockout reaction at 300A MeV , the Fermi-momentum of the bound proton, which is given by

$P_F - P_O = P_k$,

where $P_F$ is the 4-momentum of the 23F, $P_F$ is the 4-momentum of the 22O, can be very large.

The Fermi-energy of a bound nucleon has maximum 40 MeV, or the momentum can be at most 400 MeV/c. But the TGenPhaseSpace only calculate all possible solution that balancing the 4-momenta, the Fermi-momentum can be more than 1 GeV/c.

This gives an un-realistic result. For example, the proton KE can be as large as 1200 MeV, while only 300A MeV of 23F KE. In realistic situation, the maximum KE of the scattered proton is at most 300 MeV at zero degree.

Now, if we restricted the Fermi-momentum to be 100 MeV/c +- 10 MeV/c, we have,

This is more realistic result.

If we can control the distribution of Fermi-momentum, and also understand the estimation of the cross-section, we can use it to simulate many reactions.

Compiling Fortran-77 code in Ubuntu-16

Fortran-77 is a very old code, who lives in 32-bit computer.

In Ubuntu-16, the compiler g++, gcc, or gfortran are “basically the same” (as far as I understand, correct me if I am wrong.) that they only support fortran-95.

In order to compile Fortran-77 code, I tried many way, but the only way is install g77 from external source, and add -m32 for the compiling flag.

or, people can search in google by

 g77_x64_debian_and_ubuntu.tar.gz

people need to extract, change the mod of install.sh to be executable.

 tar -xzvf g77_x64_debian_and_ubuntu.tar.gz
cd g77_x64_debian_and_ubuntu
chmod +x ./install.sh
./install.sh

Somehow, you may face an error in apt-get, saying

Errors were encountered while processing:
g77-3.4-doc 

you can remove that by

cd /var/lib/dpkg/info
sudo rm g77-3.4-doc*
sudo dpkg --remove --force-remove-reinstreq g77-3.4-doc

Thanks (here)

Hope it help. :)

Algorithm of Wavelet Transform (with Qt class)

There are many kind of wavelet transform, and I think the names are quite confusing.

For instance, there are continuous and discrete wavelet transforms, in which, the “continuous” and “discrete” are for the wavelet parameters, not for the “data” itself. Therefore, for discrete data, there are “continuous” and “discrete” wavelet transforms, and for function, there are also “continuous” and “discrete” wavelet transforms.

In here, we will focus on discrete wavelet transform for function first. This discrete wavelet transform is also called as wavelet series, which express a compact support function into series of wavelet.

For simplicity, we also focus on orthonormal wavelet.

As the wavelet span the entire space, any compact function can be expressed as

$\displaystyle f(t) = \sum_{j,k} \left \psi_{j,k}(t)$

$\psi_{j,k}(t) = 2^{j/2} \psi(2^j t - k)$

where $j, k$ are integer.

Now, we move to discrete data discrete wavelet transform. The data is discrete, we can imagine only $t_n = t_0 + n \Delta$ points are known with finite $n$.

$\displaystyle f_n = f(t_n) = \sum_{j,k} \left \psi_{j,k}(t_n)$

the integration becomes a finite sum.

Without loss of generality, we can set $t_0 = 0, \Delta = 1$, and then the time axis becomes an integer number axis. We found that $j \leq 0$ as the wavelet can only be expand, not shrink. Because there are finite number of data point, i.e. $n < \infty$, $-Log_2(n) < j \leq 0$.

However, this double summation for each $f_n$ is very time consuming. There is a Fast Discrete Wavelet Transform. Before we continuous, we must study the wavelet.

From the last post, we know that the scaling function that generate a MRA must be:

$\displaystyle \phi(t) = \sum_{k} g_0(k) \phi(2t-k)$

$\left<\phi(t-k) | \phi(t-k') \right> = \delta_{kk'}$

, where $k$ are integer. The set of shifted scaling function span a space $V_0$. For the wavelet,

$\displaystyle \psi(t) = \sum_{k} g_1(k) \psi(2t-k)$

$\left<\psi(t-k) | \psi(t-k') \right> = \delta_{kk'}$

The set of shifted wavelet span a space $W_0$, so that $W_0 \perp V_0$, so that

$\left<\phi(t-k)|\psi(t-k') \right> = 0$

Since the wavelet is generated from the scaling function, we expect the coefficient of $g_0(k)$ and $g_1(k)$ are related. In fact, the relationship for orthonormal scaling function and wavelet is

$g_1(k) = (-1)^k g_0(1-k)$

For discrete data $x_i$, it can be decomposed into the MRA space. We start by the largest $V_0$ space, where the wavelet is most shrunken.

$\displaystyle x_i = \sum_{k} v_{0,k} \phi(i-k)$

to decompose to the $V_{-1}$ and $W_{-1}$ space. We can use the nested property of the MRA space, $\phi(2t)$ can be decomposed into $\phi(t-k)$ and $\psi(t-k)$,

$\displaystyle \psi(2t-l) = \sum_{k} h_0(2k-l) \phi(t-k) + h_1(2k-l) \psi(t-k)$

where (given that $\phi(t)$ and $\latex \psi(t)$ are orthonormal ),

$h_0(2k-l) = \left< \phi(2t-l) | \phi(t-k) \right>$

$h_1(2k-l) = \left< \phi(2t-l) | \psi(t-k) \right>$

Therefore, using the coefficient of $h_0$ and $h_1$, the wavelet coefficient $v_{0,k}$ can be decomposed to

$\displaystyle v_{s-1,k} = \sum_{l} h_0(2k-l) v_{s,l}$

$\displaystyle w_{s-1,k} = \sum_{l} h_1(2k-l) v_{s,l}$

in graphic representation

This is a fast discrete wavelet transform.

Due to the nested space of MRA, we also expect that the coefficient $h_0$ and $h_1$ are related to $g_0$. For orthonormal wavelet,

$\displaystyle h_0(k) = \frac{1}{2} g_0(-k)$

$\displaystyle h_1(k) = \frac{1}{2} (-1)^{k} g_0 (k+1)$

Since the $g_0$ is finite, the $g_1, h_0, h_1$ are all finite. That greatly reduce the computation cost of the discrete wavelet transform.

To reconstruct the discrete data $x_i$, we don’t need to use

$\displaystyle v_{s+1,l} = \sum_{k} v_{s,k} \phi(l - k) + w_{s,k} \psi(l-k)$

using the nested space of MRA, $\psi(t) = \sum_{k} g_1(k) \psi(2t-k)$,

$\displaystyle v_{s+1,l} = \sum_{k} g_0(l-2k) v_{s,k} + g_1(l-2k) w_{s,k}$

in graphical representation,

I attached the wavelet transfrom class for Qt, feel free to modify.