cellml / libcellml

Repository for libCellML development.
https://libcellml.org
Apache License 2.0
17 stars 21 forks source link

Tutorials for libcellml use #425

Open kerimoyle opened 5 years ago

kerimoyle commented 5 years ago

Before releasing libcellml out into the big wide world we need to have some user tutorials covering:

@nickerso suggested I follow the use cases addressed by the OpenCOR tutorials - if anyone has any suggestions for what else should be included, please let me know.

nickerso commented 5 years ago

For reference, this is the OpenCOR tutorial I'm talking about :)

https://tutorial-on-cellml-opencor-and-pmr.readthedocs.io/en/latest/

kerimoyle commented 5 years ago

I've started by trying to make some template CMake files to build simple examples which use the libcellml library, but I'm confused about how we actually want people to access them (assuming I've got the generation stuff right). I want to get the basics straight in my own head first so am writing the most bare-bones version of everything. I've turned off all the optional extras (tests, coverage etc), set it to Release, and run make -j and then make install in a local directory to install it all.

NB: Confusion about simple stuff is my speciality ... apologies for the dumb questions ...

Question 1: Do we want users to use the same#include <libcellml> idiom that we use in the tests? In which case, is that a module or a library in CMake parlance? The only way I could get any joy building my wee program was to mention the *.h files by name and specify the include path in the CMakeLists.txt ... which causes problems later (see below).

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(tutorial1 VERSION 0.1.0)

add_executable(tutorial1 main.cpp)
target_include_directories(tutorial1 PUBLIC BEFORE "/Users/kmoy001/libcellml-tutorials/library/include")

main.cpp

#include <iostream>
//#include <libcellml>        // Does not work as module or include or library?
#include <libcellml/model.h>
#include <libcellml/parser.h>

int main() {
  std::cout << "hello world\n";
  libcellml::Parser parser;
}

Question 2: Which version of C++ compiler are we supporting? There was talk recently about upgrades to 19(?) but I'm getting errors like this:

/Users/kmoy001/libcellml-tutorials/library/include/libcellml/entity.h:38:25: error: expected ';' at end of declaration list
[build]     Entity(Entity &&rhs) noexcept; /**< Move constructor */
[build]                         ^
[build]                         ;

... and warnings like this:

[build] /Users/kmoy001/libcellml-tutorials/library/include/libcellml/enumerations.h:29:6: warning: scoped enumerations are a C++11 extension [-Wc++11-extensions]
[build] enum class Prefix
kerimoyle commented 5 years ago

I've been going through the tutorial from @nickerso above and noticed that (see pg 14) it references units by their symbols as well/instead of the names in the spec table. Or is that just for clarity? If the former, is this something unique to OpenCOR or are we intending to support it in libCellML too? (and @agarny, might need to change spelling of "meter" to be "metre" in any case?)

agarny commented 5 years ago

Not sure when it comes to the tutorial, but my guess would be that it's indeed "just" for clarity. As for OpenCOR and meter vs. metre, remember that OpenCOR currently supports CellML 1.0/1.1, and both spellings are allowed. When OpenCOR officially supports CellML 2.0 then only metre will indeed be allowed.

hsorby commented 5 years ago

The CMakeLists.txt above is wrong it should look more like this:

cmake_minimum_required(VERSION 3.10.2)
project(tutorial1 VERSION 0.1.0)

# find_package(libCellML) produces a target 'cellml'
# Use -DlibCellML_DIR=<path-to-directory-containing-libcellml-config.cmake> on command line
# invocations of CMake to allow find_package to find the CellML libraries when
# installed in a non-standard location.  Otherwise when using a CMake GUI application
# add/modify the libCellML_DIR variable to the location of libCellML's
# libcellml-config.cmake file.
find_package(libCellML REQUIRED)

add_executable(tutorial1 main.cpp)
target_link_libraries(tutorial1 PUBLIC cellml)

This will fix a few of those problems you are seeing above as the tutorial1 application is not configured properly.

kerimoyle commented 5 years ago

Thanks @hsorby ... this is just the kind of thing I needed :) ... but ... on my Mac there is no option (using ccmake) to give the libCellML_DIR path. There's the LIBCELLML_INSTALL_PREFIX as the closest I can find? Or should I be specifying it somewhere else? I currently have things working (I use the word loosely ... there are lots of segfaults ...) with this:

set(PROJECT_NAME tutorial4)
cmake_minimum_required(VERSION 3.2)
project(${PROJECT_NAME} VERSION 0.1.0 LANGUAGES CXX)
set (CMAKE_CXX_STANDARD 11)
set (PROJECT_SRC
        ${PROJECT_NAME}.cpp
        ../utilities/tutorial_utilities.cpp
     )
add_executable(${PROJECT_NAME} ${PROJECT_SRC} )

target_include_directories(${PROJECT_NAME} PUBLIC  
        "/usr/local/include",
        "../utilities"
)
target_link_libraries(${PROJECT_NAME} /usr/local/lib/libcellmld.dylib)
set_target_properties(${EXE} PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE)
hsorby commented 5 years ago

But you don't have this find_package(libCellML REQUIRED) so you won't have libCellML_DIR. Your CMakeLists.txt looks nothing like mine.

kerimoyle commented 5 years ago

I should have been clearer. I realise they're different ... but when I ran yours I couldn't get the middle instructions to work as there was no option to specify the directory anywhere via the ccmake interface. That was the question: How do I pass in the libCellML_DIR path if I'm using the ccmake gui and it's not one of the options listed?

On Tue, 15 Oct 2019 at 11:36, Hugh Sorby notifications@github.com wrote:

But you don't have this find_package(libCellML REQUIRED) so you won't have libCellML_DIR. Your CMakeLists.txt looks nothing like mine.

— You are receiving this because you were assigned. Reply to this email directly, view it on GitHub https://github.com/cellml/libcellml/issues/425?email_source=notifications&email_token=ALFXCNHAYBTC2TOTQBCFBR3QOWFJDA5CNFSM4I63KSTKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBIDJ6I#issuecomment-542127353, or unsubscribe https://github.com/notifications/unsubscribe-auth/ALFXCNGBUKOUJFE7M5IJZHLQOWFJDANCNFSM4I63KSTA .

hsorby commented 5 years ago

When you first try to configure with the find_package(libCellML REQUIRED) statement it will fail. It will fail because it cannot find libCellML. When CMake configuration fails the libCellML_DIR variable will become available in the ccmake gui for you to tell CMake which directory libcellml-config.cmake resides in.

Or you can just pass libCellML_DIR in from the off and help CMake find libCellML knowing that it can't find it on it's own. That would be done using -DlibCellML_DIR=<path-to-libcellml-config.cmake>

kerimoyle commented 5 years ago

Thanks, will give it a go :)

kerimoyle commented 5 years ago

I think I'm getting there ... but can't find what I've done wrong with this setup (guessing it's in the mathml ... but if so, we really need to catch these somewhere as it's super easy to mess up writing that awful stuff!)

The test below passes, the file attached generates a segfault even though the code is the same. tutorial4.cpp.zip

TEST(Generator, tutorial4)
{
    //  1.a   Create the model instance
    libcellml::ModelPtr model = std::make_shared<libcellml::Model>();
    model->setName("Tutorial4_FirstOrderModel");

    //  1.b   Create a component and add it into the model
    libcellml::ComponentPtr component = std::make_shared<libcellml::Component>();
    component->setName("IonChannel");
    model->addComponent(component);

    //  2.a   Define the mathematics.
    std::string mathHeader = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">";

    // dy/dt = alpha_y*(1-y) - beta_y*y
    std::string equation1 =
        "<apply>\
            <eq/>\
            <apply>\
                <diff/>\
                <bvar>\
                    <ci>t</ci>\
                </bvar>\
                <ci>y</ci>\
            </apply>\
            <apply>\
                <minus/>\
                <apply>\
                    <times/>\
                    <ci>alpha_y</ci>\
                    <apply>\
                        <minus/>\
                        <cn cellml:units=\"dimensionless\">1</cn>\
                        <ci>y</ci>\
                    </apply>\
                </apply>\
                <apply>\
                    <times/>\
                    <ci>beta_y</ci>\
                    <ci>y</ci>\
                </apply>\
            </apply>\
        </apply>";
    // i_y = g_y*power(y,gamma)*(V-E_y)
    std::string equation2 =
        "<apply>\
            <eq/>\
            <ci>i_y</ci>\
            <apply>\
                <times/>\
                <ci>g_y</ci>\
                <apply>\
                    <minus/>\
                    <ci>V</ci>\
                    <ci>E_y</ci>\
                </apply>\
                <apply>\
                    <power/>\
                    <ci>y</ci>\
                    <ci>gamma</ci>\
                </apply>\
            </apply>\
        </apply>";
    std::string mathFooter = "</math>";

    //  2.b   Add the maths to the component.  Note that there is only one maths
    //        string stored, so parts which are appended must create a viable
    //        MathML2 string when concantenated.  To clear any string which is
    //        already stored, simply call setMath("") with an empty string.
    component->setMath(mathHeader);
    component->appendMath(equation1);
    component->appendMath(equation2);
    component->appendMath(mathFooter);

    //  3.a,b Declaring the variables, their names, units, and initial conditions
    //        Note that the names given to variables must be the same as that used
    //        within the <ci> blocks in the MathML string we created in step 2.a.

    libcellml::VariablePtr t = std::make_shared<libcellml::Variable>();
    t->setName("t");
    t->setUnits("millisecond");
    // Note: time is our integration base variable so is not initialised

    libcellml::VariablePtr V = std::make_shared<libcellml::Variable>();
    V->setName("V");
    V->setUnits("millivolt");
    V->setInitialValue(0.0);

    libcellml::VariablePtr alpha_y = std::make_shared<libcellml::Variable>();
    alpha_y->setName("alpha_y");
    alpha_y->setUnits("per_millisecond");
    alpha_y->setInitialValue(1.0);

    libcellml::VariablePtr beta_y = std::make_shared<libcellml::Variable>();
    beta_y->setName("beta_y");
    beta_y->setUnits("per_millisecond");
    beta_y->setInitialValue(2.0);

    libcellml::VariablePtr y = std::make_shared<libcellml::Variable>();
    y->setName("y");
    y->setUnits("dimensionless");
    y->setInitialValue(1.0);

    libcellml::VariablePtr E_y = std::make_shared<libcellml::Variable>();
    E_y->setName("E_y");
    E_y->setUnits("millivolt");
    E_y->setInitialValue(-85.0);

    libcellml::VariablePtr i_y = std::make_shared<libcellml::Variable>();
    i_y->setName("i_y");
    i_y->setUnits("microA_per_cm2");
    // Note that no initial value is needed for this variable as its value
    // is defined by equation2

    libcellml::VariablePtr g_y = std::make_shared<libcellml::Variable>();
    g_y->setName("g_y");
    g_y->setUnits("milliS_per_cm2");
    g_y->setInitialValue(36.0);

    libcellml::VariablePtr gamma = std::make_shared<libcellml::Variable>();
    gamma->setName("gamma");
    gamma->setUnits("dimensionless");
    gamma->setInitialValue(4.0);

    //  3.c Adding the variables to the component.  Note that Variables are
    //      added by their pointer (cf. their name)
    component->addVariable(t);
    component->addVariable(V);
    component->addVariable(E_y);
    component->addVariable(gamma);
    component->addVariable(i_y);
    component->addVariable(g_y);
    component->addVariable(alpha_y);
    component->addVariable(beta_y);
    component->addVariable(y);

    //  4.a Defining the units of millisecond, millivolt, per_millisecond,
    //      microA_per_cm2, and milliS_per_cm2. Note that the dimensionless
    //      units are part of those built-in already, so do not need to be
    //      defined here.
    libcellml::UnitsPtr ms = std::make_shared<libcellml::Units>();
    ms->setName("millisecond");
    ms->addUnit("second", "milli");

    libcellml::UnitsPtr mV = std::make_shared<libcellml::Units>();
    mV->setName("millivolt");
    mV->addUnit("volt", "milli");

    libcellml::UnitsPtr per_ms = std::make_shared<libcellml::Units>();
    per_ms->setName("per_millisecond");
    per_ms->addUnit("millisecond", -1.0);

    libcellml::UnitsPtr microA_per_cm2 = std::make_shared<libcellml::Units>();
    microA_per_cm2->setName("microA_per_cm2");
    microA_per_cm2->addUnit("ampere", "micro");
    microA_per_cm2->addUnit("metre", "centi", -2.0);

    libcellml::UnitsPtr mS_per_cm2 = std::make_shared<libcellml::Units>();
    mS_per_cm2->setName("milliS_per_cm2");
    mS_per_cm2->addUnit("siemens", "milli");
    mS_per_cm2->addUnit("metre", "centi", -2.0);

    //  4.b Add these units into the model
    model->addUnits(ms);
    model->addUnits(mV);
    model->addUnits(per_ms);
    model->addUnits(microA_per_cm2);
    model->addUnits(mS_per_cm2);

    //  4.c Validate the final arrangement.  No errors are expected at this stage.
    libcellml::Validator validator;
    validator.validateModel(model);
    printErrors(validator);

    //  5.a   Create a Generator instance.  By default the options set in the
    //        generator constructor are:
    //          - profile() return "C" (cf "PYTHON")
    //          - modelType() returns "ODE"

    libcellml::Generator generator;
    generator.processModel(model);

    //  5.b Check whether the generator has encountered any errors
    std::cout << "The generator returned " << generator.errorCount() << " errors!" << std::endl;
    for (size_t e = 0; e < generator.errorCount(); e++) {
        std::cout << generator.error(e)->description() << std::endl;
    }

    EXPECT_EQ(size_t(0), generator.errorCount());
}
hsorby commented 5 years ago

Have managed to create an isolated test which shows the segfault here not got any further than confirming the problem as a test.

This branch has the same test twice once in generator/generator.cpp and isolated/generator.cpp. The first instance of the test in generator/generator.cpp doesn't segfault while the second isolated/generator.cpp does!

kerimoyle commented 5 years ago

Thanks Hugh. I've found I'm getting a LOT more segfaults when using the library externally ... used to not get any just from the tests. It must be that magical compiler switch GENERATE_SEGFAULTS which is set to TRUE somewhere ...