Hello again,

In a much needed attempt to deliver more meaningful results before GSoC’s Second Evaluation, today I’m applying information from the previous post on actual generation of a Modelica-based Scicos block, using OpenModelica.

For reasons beyond my knowledge (no sarcasm intended), Scilab currently uses OCaml language for writing the executables involved in Modelica simulation. And, as they seem to be the only pieces that need a OCaml compiler available to be built, being able to replace them completely by OMCompiler looks like a nice dependency tradeoff.

Modelica Code Generation

The first step in Modelica simulation (at least after construction of the diagram structure) is generation of code. During the first compilation pass (at c_pass1.sci script), all found Modelica blocks are added to a list to be combined into a single one by build_modelica_block.sci function:

function [model,ok] = build_modelica_block( blklstm, corinvm, cmmat, NiM, NoM, NvM, scs_m, path )
    // Given the blocks definitions in blklstm and connections in cmmat this
    // function first create  the associated modelicablock  and writes its code
    // in the file named 'imppart_'+name+'.mo' in the directory given by path
    // Then modelica compiler is called to produce the C code of scicos block
    // associated to this modelica block. filbally the C code is compiled and
    // dynamically linked with Scilab.
    // The correspondind model data structure is returned.


    // Get the name of the generated main modelica file
    name = stripblanks( scs_m.props.title(1) ) + "_im";

    // Generation of the txt for the main modelica file
    // plus return ipar/rpar for the model of THE modelica block
    [txt, rpar, ipar] = create_modelica( blklstm, corinvm, cmmat, NvM, name, scs_m );

    // Write txt in the file path+name+'.mo'
    path = pathconvert( stripblanks( path ), %t, %t );
    mputl( txt, path + name + ".mo" );

    // Append data from all Modelica blocks found to list
    Mblocks = [];
    for i=1:lstsize( blklstm )
        if type( blklstm(i).sim ) == 15 then
            if blklstm(i).sim(2) == 30004 then
                o = scs_m( scs_full_path( corinvm(i) ) );
                Mblocks=[ Mblocks;
                          o.graphics.exprs.nameF ];
            end
        end
    end

    // Generate XML and model_flat_Model
    // Compile modelica files to binary library
    [ok,name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = compile_modelica( path + name + ".mo", Mblocks );

    if ~ok then return,end

    // Build model data structure of the block equivalent to the implicit part
    model = scicos_model( sim=list( name, 10004 ),..
    label=name, uid=guid,..
    in=ones( nin, 1 ), out=ones( nout, 1 ),..
    state=zeros( nx*2, 1 ),..
    dstate=zeros( nz, 1 ),..
    rpar=rpar,..
    ipar=ipar,..
    dep_ut=[ dep_u %t ],nzcross=ng,nmode=nm );
endfunction

Inside create_modelica functions (defined at create_modelica.sci), information from all Modelica blocks is combined (considering the interconnection matrices passed as arguments) and a single .mo source file is generated. All this process is performed in Scilab script and is independent of our implementation, meaning that it remains untouched.

Model Flattening

By default, a Scilab installation already contains Modelica libraries of Mechanical and Electrical components (that could be expanded by toolboxes like Coselica) to be used by more complex models. After generation of the superblock code, passed to compile_modelica function, the relevant (utilized) parts of those libraries are also added to the main file by translator function, creating the final flat model:

function [ok,name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = compile_modelica( filemo, Mblocks )

    // ...

    // Verify if simulation uses analytical Jacobian matrix
    if exists("%Jacobian") == 0 then %Jacobian=%t; end

    //Initialize lhs arguments in case of return on error
    name=""; guid="";                           // Model name and GUID
    dep_u=%t; nipar=0; nrpar=0; nopar=0;        // Input/output dependency and number of parameters
    nz=0; nx=0; nx_der=0; nx_ns=0;              // Number of states and derivatives
    nin=0; nout=0;                              // Number of inputs and outputs
    nm=0; ng=0;                                 // Number of modes and zero-crossing surfaces

    //set paths for generated files
    outpath = pathconvert( TMPDIR, %t, %t );

    model_name = basename( filemo );
    model_name_flat = model_name + "f";

    // Flat modelica file generated by translator
    model_flat_file = outpath + model_name_flat + ".mo";

    // Files/folders generated by modelica compiler
    model_desc_file = outpath + model_name + "/modelDescription.xml"; // Model description XML file
    model_C_file = outpath + model_name + ".c"                        // C code file generated for flat model

    // ...

    // Generate with filemo and Modelica libraries the flat model
    ok = translator( filemo, Mblocks, model_flat_file );
    if ~ok then  dep_u=%t; return,end

    // ...

Inside translator, the OCaml-generated modelicat executable is replaced by OMCompiler, which offers the same flattening capabilities:

function ok = translator( filemo, Mblocks, Flat )
    //Generate the flat model of the Modelica model given in the filemo file
    //and the Modelica libraries. Interface to the external tool
    //translator.

    // Get OMcompiler executable name
    TRANSLATOR_FILENAME = "omc";
    if getos() == "Windows" then
        TRANSLATOR_FILENAME = TRANSLATOR_FILENAME + ".exe";
    end

    // Get libraries and output paths
    [ modelica_libs, modelica_directory ] = getModelicaPath();

    // Find all needed libraries
    // ...

    translator_libs = " """ + filemo + """ " + translator_libs;

    //Build the shell instruction for calling the translator

    exe = getmodelicacpath() + TRANSLATOR_FILENAME
    exe = """" + pathconvert(getmodelicacpath() + TRANSLATOR_FILENAME,%f,%t) + """ ";

    out =" """ + Flat + """" // Flat modelica

    // Shell instruction for generating flat Modelica code and saving it to a file
    // --modelicaOutput sets output to be a modelica source file
    // --useLocalDirection preserves the input/output properties from library variables
    // --reduceTerms remove input/output redundancies
    instr = exe + " " + translator_libs + " --modelicaOutput --useLocalDirection --reduceTerms > " + out;

    // ...

    // Instruction call and error handling
    [rep,stat,err]=unix_g(instr);
    if stat <> 0 then
        messagebox(err, _("Modelica translator"), "error", "modal");
        ok=%f;
        return
    end

    ok = %t

endfunction

C code generation

Next, FMU package and FMI2 C wrapper code are generated from flat model:

function [ok,name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = compile_modelica( filemo, Mblocks )

    // ...

    //Generate the C file with modelicac
    ok = modelicac( model_flat_file, %Jacobian, running=="1", model_C_file, model_desc_file );
    if ~ok then return,end

    // Read XML file data into a tree-like native data structure
    [name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = reading_incidence( model_desc_file );

    // ...

    //compile and link the generated C file
    ok = Link_modelica_C( model_C_file );

Inside modelicac function (the name was kept the same as in previous implementation), OMCompiler is once again used, running the FMU package creation and extraction .mos script. Also, our C code, already explained in past publications, is used on this step:

function  ok = modelicac( model_flat_file, Jacobian, model_C_file, model_desc_file )
    //Scilab interface with external tool modelicac

    MODELICAC_FILENAME = "omc";
    if getos() == "Windows" then
        MODELICAC_FILENAME = MODELICAC_FILENAME + ".exe";
    end

    [model_path, model_name, C_ext] = fileparts(model_C_file);
    [model_path, model_flat_name, flat_ext] = fileparts(model_flat_file);

    model_flat_script = pathconvert(model_path + model_name + ".mos", %f, %t); mprintf("modelicac: script file: %s\n", model_flat_script);
    model_flat_package = pathconvert(model_path + model_name + ".fmu", %f, %t); mprintf("modelicac: packege file: %s\n", model_flat_package);

    current_dir = pwd();
    chdir( model_path );

    if getos() == "Windows" then
        extract_cmd = getshortpathname(pathconvert(SCI+"/tools/zip/unzip.exe",%F)) + " -o ";
    else
        extract_cmd = "unzip -o ";
    end

    // Generate OpenModelica script file (builds FMU package with partial derivative support and unzips it)
    compile_commands = [];
    compile_commands($ + 1) = "setModelicaPath(""" + model_path + """); getErrorString();"
    // Try to use Visual Studio's native compiler on Windows systems
    if getos() == "Windows" then
        compile_commands($ + 1) = "setCommandLineOptions(""+target=msvc""); getErrorString();";
    end
    if Jacobian then
        compile_commands($ + 1) = "setDebugFlags(""fmuExperimental""); getErrorString();";
    end
    compile_commands($ + 1) = "loadFile(""" + model_flat_name + flat_ext + """); getErrorString();";
    compile_commands($ + 1) = "translateModelFMU(" + model_name + "); getErrorString();";
    compile_commands($ + 1) = "system(""" + extract_cmd + model_flat_package + """); getErrorString();";
    mputl( compile_commands, model_flat_script );

    exe = """" + pathconvert(getmodelicacpath() + MODELICAC_FILENAME, %f, %t) + """";
    mprintf("modelicac: executable: %s\n", MODELICAC_FILENAME);
    model_flat_script = """" + model_flat_script + """";

    instr = strcat([exe, model_flat_script], " ");

    // ...

    [rep,stat,err]=unix_g(instr);

    chdir( current_dir );

    if stat <> 0 then
        messagebox(err, _("Modelica compiler"), "error", "modal");
        ok=%f;
        return
    end

    //Modelica library C code wrapper for the simulation function
    fmi2_wrapper_code = generate_fmi2_wrapper(model_desc_file);
    mputl(fmi2_wrapper_code, model_C_file);

endfunction

function code = generate_fmi2_wrapper(model_desc_file)
    // Get variable references lists from description file
    model_desc_tree = xmlRead(model_desc_file);
    [name,guid,in_refs,x_refs,x_der_refs,par_refs,out_refs]=read_modelica_variables(model_desc_tree);

    in_length = max(1, length(in_refs));
    x_length = max(1, length(x_refs));
    x_der_length = max(1, length(x_der_refs));
    out_length = max(1, length(out_refs));
    par_length = max(1, length(par_refs));

    // Generate C code for specific fixed size variables lists and append generic FMI2 wrapper code
    code = [
               "#include ""fmi2Functions.h""",
               "#include ""fmi2FunctionTypes.h""",
               "#include ""fmi2TypesPlatform.h""",
               "",
               strcat(["static fmi2Real inputsList[ ", string(in_length), " ] = { 0.0 };"]),
               strcat(["static const fmi2ValueReference INPUT_REFS_LIST[ ", string(in_length), " ] = { ", strcat(string(in_refs), ","), " };"]),
               strcat(["static const fmi2ValueReference STATE_REFS_LIST[ ", string(x_length), " ] = { ", strcat(string(x_refs), ","), " };"]),
               strcat(["static fmi2Real stateDerivativesList[ ", string(x_der_length), " ] = { 0.0 };"]),
               strcat(["static const fmi2ValueReference STATE_DER_REFS_LIST[ ", string(x_der_length), " ] = { ", strcat(string(x_der_refs), ","), " };"]),
               strcat(["static fmi2Real outputsList[ ", string(out_length), " ] = { 0.0 };"]),
               strcat(["static const fmi2ValueReference OUTPUT_REFS_LIST[ ", string(out_length), " ] = { ", strcat(string(out_refs), ","), " };"]),
               strcat(["static fmi2Real parametersList[ ", string(par_length), " ] = { 0.0 };"]),
               strcat(["static const fmi2ValueReference PARAMETER_REFS_LIST[ ", string(par_length), " ] = { ", strcat(string(par_refs), ","), " };"]),
               "",
               strcat(["#define BLOCK_FUNCTION_NAME ", name]),
               strcat(["#define MODEL_NAME """, name, """"]),
               strcat(["#define MODEL_GUID """, guid, """"]),
               "",
               "#include ""fmi2_wrapper.h"""
           ];

endfunction

Notice that our FMI2 layer code is put into a header (.h) file now (located in /system_includes_dir/scilab/), as binary distributions of Scilab doesn’t ship sources (.c). Also, as we have to generate the actual C source file including the wrapper, this gives the opportunity to add the variable references (indexing inputs, outputs, states, etc…) vectors that couldn’t be passed to the computational function otherwise.

Model Description Parsing

After that, modelDescription.xml file produced by OMCompiler is parsed in order to extract all properties needed for the Scicos block:

function [ok,name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = compile_modelica( filemo, Mblocks )

    // ...

    // Read XML file data into a tree-like native data structure
    model_desc_tree = xmlRead( model_desc_file );

    [name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = reading_incidence( model_desc_file )

    // ...
function [name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = reading_incidence( model_desc_file )
    // this function creates the matrix dep_u given by the xml format.
    // It is used for modelica compiler.
    // number of lines represents the number of input, number of columns represents the number of outputs.

    model_desc_tree = xmlRead( model_desc_file );
    [name,guid,in_refs,x_refs,x_der_refs,par_refs,out_refs] = read_modelica_variables( model_desc_tree );

    nipar = 0; nopar = 0; nz = 0; nx_ns = 0; nm = 0;
    nin = length(in_refs);
    nx = length(x_refs);
    nx_der = length(x_der_refs);
    nout = length(out_refs);
    nrpar = length(par_refs);

    // Aquire and display number of model's event indicators
    ng = round( strtod( model_desc_tree.root.attributes( "numberOfEventIndicators" ) ) );

    // Output includes state, so is it always depends on existing input
    if nin > 0 then
        dep_u = %t
    else
        dep_u = %f
    end

endfunction

Here, read_model_variables works much like in the previous post.

Model Library Linkage

Finally, system’s native compiler is called for building the wrapped FMI2 library is link it to simulator main executable:

function [ok,name,guid,nipar,nrpar,nopar,nz,nx,nx_der,nx_ns,nin,nout,nm,ng,dep_u] = compile_modelica( filemo, Mblocks )

    // ...

    //compile and link the generated C file
    ok = Link_modelica_C( model_C_file );

endfunction
function ok = Link_modelica_C( model_C_file, model_path )

    model_C_file = pathconvert(model_C_file, %f, %t)
    [model_path, model_name, ext] = fileparts(model_C_file);

    // Determine binary libraries directory according to platform

    [version, opts] = getversion();
    compiler = opts(1)
    if opts(2) == "x64" then
        arch = "64"
    else
        arch = "32"
    end

    model_libs_path = model_path + "/" + model_name + "/binaries/";
    if getos() == "Windows" then
        model_libs_path = model_libs_path + "/win" + arch + "/";
    else
        model_libs_path = model_libs_path + "/linux" + arch + "/";
    end

    // Define FMI2 headers include directory
    model_include_path = model_path + "/" + model_name + "/sources/include/fmi2/";

    // Add linked libraries list
    model_libs = [ model_libs_path + model_name + getdynlibext() ];

    // ...

    // Define list of directories to be searched for *.h
    Cpp_flags = "  -I""" + model_include_path + """";

    // Call common function for building block shared library from C code
    ok = buildnewblock(model_name, model_name, "", "", model_libs, TMPDIR, "", IncludePaths);

endfunction

Final Considerations

The implementation showed here still has rough edges, as it is troublesome to understand the purpose of all variables and operations involved in the original codebase, and it’s not properly tested. Even so, it constitutes the bulk of the work involved in interfacing Scilab with OpenModelica’s compiler. Over time, this post will be updated with eventual refinements applied to the code.

That’s it for now. Thanks one more time for sticking by. See you soon !