TRNSYSコンポーネントのPrintデバッグ

あまり需要がある話とは思えないですが、備忘のためまとめておきます。

プログラムをデバッグする方法としてPrintデバッグというのがあります。名前の通り処理の途中にデバッグ用の出力を入れて画面やファイルに書き出して値をチェックするという古典的な方法です。

ファイルに書き出す方法として、TRNSYSにはリストファイル(.lst)へ書き出すAPI、writeToList()が用意されています。これを使って、次のようにコーディングするとリストファイルへの書き出しが行えます。

power_rated = getParameterValue(3)
Call writeToList(‘—– PRINT DEBUG —–‘)   ! コメントの書き出し
write (messageText,'(f10.3)’) power_rated   ! 値を文字列に変換
Call writeToList(messageText)                     ! .lstファイルへ書き出し

なぜか今朝、コンパイラのデバッグモードが使えなくなってしまったため、Printデバッグを使って見ました。TypeStudioでデバッグしようとすると、いまのところこの方法かな?

動作環境

以下の環境で動作を確認しています。
Windows10 Pro(64bit, 1803)
TRNSYS18.00.0019(64bit)

TRNSYSでCoolPropをサポート

TRNSYS18の資料にCoolPropのサポートの記載があるのですが、で、CoolPropって何?ググってみたら、どうもこれっぽい。

CoolProp

CoolProp
CoolProp

オープンソースの流体特性ライブラリ(?)C++のライブラリっぽいですが、Python , Modelica, Octave, C#, VB.net, MATLABなどから呼び出すための各種ラッパーが用意されています。手軽に扱うことが可能なようですが、FORTRANもサポートされていて、どうもTRNSYSの関数にCoolProp対応のものが用意されるようです。(合ってんのかな?)

ちなみにWebで公開されているCoolProp Onlineもあります。雰囲気を掴むには良さそうなサイトです。CoolProp Online

CoolProp Online
CoolProp Online

条件を指定すると、チャートを表示してくれる。

Calling TRNSYS Functions from C/C++

TRNSYSの関数をC/C++から呼び出す方法について紹介します。というか備忘です。
In this article I will explain how to call TRNSYS functions from C/C++.

1. 引数/argument

C/C++では引数は下表のように置き換える必要があります。
In the C/C++ language, the argument needs to be replaced as shown in the table below.

 

FORTRANC/C++
Integerint*
Real(8),
Double Precision
double*
Character(Len=n)char*, size_t n

コーディング例

1.1. Integer

– TRNSYS.h

extern “C” __declspec(dllimport) void _cdecl SETTYPEVERSION(int* ver);
#define setTypeVersion                 SETTYPEVERSION

– Source code

if (getIsVersionSigningTime())
{
     int ver = 17;
     setTypeVersion(&ver);
     return 1;
}

1.2. Double Precision

– TRNSYS.h

extern “C” __declspec(dllimport) double _cdecl TRNSYSFUNCTIONS_mp_GETINPUTVALUE(int* n);
#define getInputValue                   TRNSYSFUNCTIONS_mp_GETINPUTVALUE

– Source code

int no = 1;
double ret = getInputValue(&no);

1.2. Character

文字列を渡す引数では、size_t(文字列の長さ)を引数の最後に加える。
NOTE: For string argument, the length(size_t) has to be added at the end of the arguments.

– TRNSYS.h

extern “C” __declspec(dllimport) void _cdecl SETINPUTUNITS(int*i, char* string, size_t len);
#define setInputUnits                   SETINPUTUNITS

– Source code

int i = 1;
setInputUnits(&i, “DM1”, 3);  

2. 戻り値/return value

戻り値は下表のように置き換えます。
In the C/C++ language, the return value needs to be replaced as shown below.

 

FORTRANC/C++
Logicalint
Integerint
Real(8),
Double Precision
double
Character(Len=n)char* , size_t n

コーデング例

2.1. Logical

– TRNSYS.h

extern “C” __declspec(dllimport) int    _cdecl TRNSYSFUNCTIONS_mp_ERRORFOUND(void);
#define ErrorFound                     TRNSYSFUNCTIONS_mp_ERRORFOUND  

– Source code

if (ErrorFound()) return 1; // 0:false, except 0: true in C/C++

2.2. Integer

– TRNSYS.h

extern “C” __declspec(dllimport) int _cdecl TRNSYSFUNCTIONS_mp_GETMAXLABELLENGTH(void);
#define getMaxLabelLength        TRNSYSFUNCTIONS_mp_GETMAXLABELLENGTH  

– Source code

size_t maxlen = getMaxLabelLength();

2.3. Double Precision

– TRNSYS.h

extern “C” __declspec(dllimport) double _cdecl TRNSYSFUNCTIONS_mp_GETINPUTVALUE(int* n);
#define getInputValue                TRNSYSFUNCTIONS_mp_GETINPUTVALUE

– Source code

int no = 1;
double ret = getInputValue(&no);

2.4 Character

※文字列(Character型)を返す関数では、「引数」としてchar* と size_t(文字列の長さ)を第1、第2引数として指定する。
NOTE: For a function which returns ‘Character’, char* and size_t has to be needed as the first and second arguments in C/C++.

– TRNSYS.h

extern “C” __declspec(dllimport) int  _cdecl TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH(void);
extern “C” __declspec(dllimport) char*  _cdecl TRNSYSFUNCTIONS_mp_GETDECKFILENAME(char* dck, size_t len);

#define getMaxPathLength         TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH 
#define getDeckFileName           TRNSYSFUNCTIONS_mp_GETDECKFILENAME

– Source code

size_t maxlen = getMaxPathLength();
char *fname= new char[maxlen];
getDeckFileName(fname, maxlen);
std::string deckFileName = trim(std::string(fname,0,maxlen)); // trimming the string, just in case
delete[] fname; // Trimming the string
std::string trim(const std::string& str)
{
    size_t first = str.find_first_not_of(‘ ‘);
    if (std::string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(‘ ‘);
    return str.substr(first, (last – first + 1));
}

動いたけど半信半疑

TRNSYSでコンポーネントを作る際には、計算そのものの実装の他、計算に使う入力やパラメータなどの値をTRNSYSの本体(Kernel)から取得します。この処理はTRNSYSに予め用意されている関数を使って行います。ほとんど場合は実数値を取得するのですが、まれに文字列を取得したいことがあります。例えば実行中のDckファイルをコンポーネントで取得する場合、getDeckFileName()という関数を利用します。

C/C++から関数を呼び出す

TRNSYSの関数はFOTRANからの呼び出しを前提にしています。このためC/C++のような別の言語からの呼び出しは一工夫要ります。
もっとも実数値であれば慣れれば、それほど難しくもないのですが、困るのが文字列の扱いです。そのあたりの事情と対策は以前にまとめています。 参考:TRNSYSの関数をC/C++から呼び出すと動かない この方法でFORTRAN側でC/C++用の関数を用意することで対応はできるものの、TRNDLL.DLLのビルドが必要になります。それはそれでハードルが高い。なにかC/C++だけでなんとかならないかと思っていたのですが、たまたま見たサイトに、そのものずばりを見つけました。

RETURNING A CHARACTER STRING
Passing strings between C and Fortran routines is not encouraged. However, a Fortran character-string-valued function is equivalent to a C function with two additional first arguments–data address and string length. The general pattern for the Fortran function and its corresponding C function is:

Fortran function
CHARACTER*n FUNCTION C(a1, …, an)

C function
void c_ (result, length, a1, …, an)
char result[ ];
long length;

出典:Fortran Programming Guide Chapter 11 C-Fortran Interface 

この出典のリンク先の最後の方に記載があります。

なんと関数呼び出しの引数に文字列と、その長さを受け取るための変数を追加するだけ。こんなシンプルなというか、暗黙の変換があるのがそもそも驚きです。(リンク先の記載の感じだと割と一般的な処理っぽい)半信半疑でgetDeckFileName()を例にヘッダーを書き換えてみます。

extern “C” __declspec(dllimport) int    _cdecl TRNSYSFUNCTIONS_mp_GETMAXPATHLENGTH(void);
extern “C” __declspec(dllimport) char*    _cdecl TRNSYSFUNCTIONS_mp_GETDECKFILENAME(char* dck, size_t len);

赤い文字の部分が書き加えた部分です。getDeckFileName()に加えて、getMaxPathLength()も書き換えています。これはファイル名を取得する際にファイル名の最大長が必要になるため使用します。(ヘッダーの雛形の定義が間違っていて、そのまま使うとエラーになります。かならず書き換えましょう)

そしてC/C++から呼び出す

実際にC/C++から呼び出してみた例が以下になります。
fnameにはNULL終端していない固定長の文字列が返ってくるので、念のためトリミングの処理をしています。

これで最終的に std::string deckFileName にトリミングされたファイル名が入っていればOKという事になります。

size_t maxlen = getMaxPathLength(); // ファイル名の文字列の最大長を取得
char *fname= new char[maxlen];  // ファイル名を受け取る変数を用意する
getDeckFileName(fname, maxlen); // ファイル名を取得
std::string deckFileName = trim(std::string(fname,0,maxlen)); // 念のためトリミングする
delete[] fname; // 変数を削除

ちなみにトリミング処理は、こんな感じで実装

std::string trim(const std::string& str)
{
    size_t first = str.find_first_not_of(‘ ‘);
    if (std::string::npos == first)
    {
        return str;
    }
    size_t last = str.find_last_not_of(‘ ‘);
    return str.substr(first, (last – first + 1));
}

結果

で、実際動かしてみたら無事に目的の文字列が取れました。

dckファイルの名前を取得する
dckファイルの名前を取得する

拍子抜けするぐらいあっさりと動きました。逆に不安になるぐらい。これで長年の課題がようやく解決できた。(本当か?)