diff --git a/.coveragerc b/.coveragerc
index c44b590a..7016ba48 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -8,3 +8,7 @@ source =
omit =
# Generated code
muscle_manager_protocol/muscle_manager_protocol_pb2*
+
+[xml]
+
+output = cobertura.xml
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000..80ba2e99
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,32 @@
+# Run Continuous Integration on every push
+name: continuous_integration
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Set up Python 3.6
+ uses: actions/setup-python@v1
+ with:
+ python-version: 3.6
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get install -y build-essential cmake gfortran libopenmpi-dev pkg-config wget
+ sudo apt-get install -y libssl-dev zlib1g-dev
+ pip install ymmsl==0.10.1
+
+ - name: Build and run the test suite
+ env:
+ MUSCLE_ENABLE_MPI: 1
+ run: |
+ cd $GITHUB_WORKSPACE
+ make test
+
+ - name: Upload coverage report to Codacy
+ uses: codacy/codacy-coverage-reporter-action@master
+ with:
+ project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
diff --git a/.github/workflows/ci_python3.5.1.yaml b/.github/workflows/ci_python3.5.1.yaml
new file mode 100644
index 00000000..57799ba5
--- /dev/null
+++ b/.github/workflows/ci_python3.5.1.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.5.1
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.5.1 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.5.1-eggs
+
+ - name: Run Python tests on 3.5.1 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.5.1 /bin/bash -c 'cd /home/muscle3 && pip install -U pip setuptools wheel ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_python3.5.yaml b/.github/workflows/ci_python3.5.yaml
new file mode 100644
index 00000000..bb4232eb
--- /dev/null
+++ b/.github/workflows/ci_python3.5.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.5
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.5 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.5-eggs
+
+ - name: Run Python tests on 3.5 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.5 /bin/bash -c 'cd /home/muscle3 && pip install ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_python3.6.1.yaml b/.github/workflows/ci_python3.6.1.yaml
new file mode 100644
index 00000000..75fe0a25
--- /dev/null
+++ b/.github/workflows/ci_python3.6.1.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.6.1
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.6.1 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.6.1-eggs
+
+ - name: Run Python tests on 3.6.1 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.6.1 /bin/bash -c 'cd /home/muscle3 && pip install -U pip setuptools wheel ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_python3.6.yaml b/.github/workflows/ci_python3.6.yaml
new file mode 100644
index 00000000..eab001e9
--- /dev/null
+++ b/.github/workflows/ci_python3.6.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.6
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.6 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.6-eggs
+
+ - name: Run Python tests on 3.6 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.6 /bin/bash -c 'cd /home/muscle3 && pip install ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_python3.7.yaml b/.github/workflows/ci_python3.7.yaml
new file mode 100644
index 00000000..88fbd066
--- /dev/null
+++ b/.github/workflows/ci_python3.7.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.7
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.7 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.7-eggs
+
+ - name: Run Python tests on 3.7 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.7 /bin/bash -c 'cd /home/muscle3 && pip install ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_python3.8.yaml b/.github/workflows/ci_python3.8.yaml
new file mode 100644
index 00000000..549b2d38
--- /dev/null
+++ b/.github/workflows/ci_python3.8.yaml
@@ -0,0 +1,19 @@
+# Run Continuous Integration on every push
+# This version tests only Python, but on all supported versions of it.
+name: python_compatibility_3.8
+on: [push, pull_request]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Cache Python 3.8 eggs
+ uses: actions/cache@v1
+ with:
+ path: ${{ github.workspace }}/.eggs
+ key: python-compatibility-3.8-eggs
+
+ - name: Run Python tests on 3.8 latest
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" python:3.8 /bin/bash -c 'cd /home/muscle3 && pip install ymmsl==0.10.1 && make test_python_only'
diff --git a/.github/workflows/ci_ubuntu16.04.yaml b/.github/workflows/ci_ubuntu16.04.yaml
new file mode 100644
index 00000000..1b29991f
--- /dev/null
+++ b/.github/workflows/ci_ubuntu16.04.yaml
@@ -0,0 +1,15 @@
+# Run Continuous Integration for the latest Ubuntu release
+# This mainly checks for issues/regressions in the native build
+name: native_compatibility_ubuntu16.04
+on:
+ schedule:
+ - cron: '0 1 * * 0'
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run tests on Ubuntu 16.04
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" --env LC_ALL=C.UTF-8 --env LANG=C.UTF-8 --env DEBIAN_FRONTEND=noninteractive ubuntu:16.04 /bin/bash -c 'apt-get update && apt-get -y dist-upgrade && apt-get -y install build-essential cmake gfortran libopenmpi-dev pkg-config python3 python3-pip python3-venv curl && apt-get -y remove libssl-dev zlib1g-dev && pip3 install -U pip setuptools wheel && cd /home/muscle3 && pip3 install ymmsl==0.10.1 && make test_examples'
diff --git a/.github/workflows/ci_ubuntu18.04.yaml b/.github/workflows/ci_ubuntu18.04.yaml
new file mode 100644
index 00000000..86c79e8c
--- /dev/null
+++ b/.github/workflows/ci_ubuntu18.04.yaml
@@ -0,0 +1,15 @@
+# Run Continuous Integration for the latest Ubuntu release
+# This mainly checks for issues/regressions in the native build
+name: native_compatibility_ubuntu18.04
+on:
+ schedule:
+ - cron: '0 2 * * 0'
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run tests on Ubuntu 18.04
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" --env LC_ALL=C.UTF-8 --env LANG=C.UTF-8 --env DEBIAN_FRONTEND=noninteractive ubuntu:18.04 /bin/bash -c 'apt-get update && apt-get -y dist-upgrade && apt-get -y install build-essential cmake gfortran libopenmpi-dev pkg-config python3 python3-pip python3-venv curl && apt-get -y remove libssl-dev zlib1g-dev && pip3 install -U pip setuptools wheel && cd /home/muscle3 && pip3 install ymmsl==0.10.1 && make test_examples'
diff --git a/.github/workflows/ci_ubuntu19.10.yaml b/.github/workflows/ci_ubuntu19.10.yaml
new file mode 100644
index 00000000..6f220106
--- /dev/null
+++ b/.github/workflows/ci_ubuntu19.10.yaml
@@ -0,0 +1,15 @@
+# Run Continuous Integration for the latest Ubuntu release
+# This mainly checks for issues/regressions in the native build
+name: native_compatibility_ubuntu19.10
+on:
+ schedule:
+ - cron: '0 3 * * 0'
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run tests on Ubuntu 19.10
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" --env LC_ALL=C.UTF-8 --env LANG=C.UTF-8 --env DEBIAN_FRONTEND=noninteractive ubuntu:19.10 /bin/bash -c 'apt-get update && apt-get -y dist-upgrade && apt-get -y install build-essential cmake gfortran libopenmpi-dev pkg-config python3 python3-pip python3-venv curl && apt-get -y remove libssl-dev zlib1g-dev && pip3 install -U pip setuptools wheel && cd /home/muscle3 && pip3 install ymmsl==0.10.1 && make test_examples'
diff --git a/.github/workflows/ci_ubuntu20.04.yaml b/.github/workflows/ci_ubuntu20.04.yaml
new file mode 100644
index 00000000..c46af0f9
--- /dev/null
+++ b/.github/workflows/ci_ubuntu20.04.yaml
@@ -0,0 +1,15 @@
+# Run Continuous Integration for the latest Ubuntu release
+# This mainly checks for issues/regressions in the native build
+name: native_compatibility_ubuntu20.04
+on:
+ schedule:
+ - cron: '0 4 * * 0'
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run tests on Ubuntu 20.04
+ run: docker run -v "${GITHUB_WORKSPACE}:/home/muscle3" --env LC_ALL=C.UTF-8 --env LANG=C.UTF-8 --env DEBIAN_FRONTEND=noninteractive ubuntu:20.04 /bin/bash -c 'apt-get update && apt-get -y dist-upgrade && apt-get -y install build-essential cmake gfortran libopenmpi-dev pkg-config python3 python3-pip python3-venv curl && apt-get -y remove libssl-dev zlib1g-dev && pip3 install -U pip setuptools wheel && cd /home/muscle3 && pip3 install ymmsl==0.10.1 && make test_examples'
diff --git a/.gitignore b/.gitignore
index 0d4a9847..86766ea7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,7 +11,7 @@
htmlcov
.coverage
-coverage.xml
+cobertura.xml
docs/doxygen
docs/apidocs
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 776d1aa6..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-dist: xenial
-language: python
-python:
- - "3.6"
- - "3.7"
-
-# Travis comes with an old pytest that does not work with pytest-flake8
-# So upgrade that first, and also install dependencies from binaries as much
-# as possible to speed things up. We also need a Fortran compiler.
-before_install:
- - sudo apt-get install -y libssl-dev zlib1g-dev gfortran
- - pip install --upgrade pytest
- - pip install ymmsl==0.10.0
-
-install:
- - pip install codacy-coverage
-
-script: make test
-
-after_success:
- - python-codacy-coverage -r coverage.xml
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 6d74216d..25dbf0d2 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,30 @@ Change Log
All notable changes to this project will be documented in this file.
This project adheres to `Semantic Versioning `_.
+0.3.1
+*****
+
+Added
+-----
+
+* Support for sending and receiving multidimensional grids/arrays
+* Support for Python 3.8
+
+Improved
+--------
+
+* Python 3.5.1 support
+* Build compatibility on more operating systems
+
+Thanks
+------
+
+* Olivier for testing, reporting and fixing build issues
+* Pavel for testing and reporting build issues
+* Hamid for testing and reporting build issues
+* Ben for testing and reporting build issues
+
+
0.3.0
*****
diff --git a/CITATION.cff b/CITATION.cff
index 390458b8..d576d1a0 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -4,7 +4,7 @@ authors:
-
family-names: Veen
given-names: Lourens
- orcid: "https://orcid.org/000-0002-6311-1168"
+ orcid: "https://orcid.org/0000-0002-6311-1168"
cff-version: "1.1.0"
doi: "10.5281/zenodo.3258864"
keywords:
diff --git a/Doxyfile b/Doxyfile
index 214e6ed4..f0c512dd 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -2099,7 +2099,7 @@ INCLUDE_FILE_PATTERNS =
# recursively expanded use the := operator instead of the = operator.
# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
-PREDEFINED = MUSCLE_ENABLE_MPI
+PREDEFINED = MUSCLE_ENABLE_MPI DOXYGEN_SHOULD_SKIP_THIS
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The
diff --git a/Makefile b/Makefile
index bf6baef1..dff04cad 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,11 @@
-cpp_test_files := libmuscle/cpp/build/ymmsl/tests/test_* libmuscle/cpp/build/libmuscle/tests/test_*
+export TOOLDIR := $(CURDIR)/scripts/gmake
+
+version_file := $(CURDIR)/VERSION
+export muscle_version := $(shell cat $(version_file))
+export major_version := $(shell sed -e 's/^\([0-9]*\)\..*/\1/' $(version_file))
+export minor_version := $(shell sed -e 's/^[0-9]*\.\([0-9]*\)\..*/\1/' $(version_file))
+export patch_version := $(shell sed -e 's/^[0-9]*\.[0-9]*\.\([0-9]*\).*/\1/' $(version_file))
+
.PHONY: all
all: cpp fortran
@@ -11,22 +18,35 @@ all: cpp fortran
.PHONY: test
test: test_python test_scripts test_cpp test_fortran
+.PHONY: test_python_only
+test_python_only:
+ MUSCLE_TEST_PYTHON_ONLY=1 python3 setup.py test
+
.PHONY: test_python
test_python: cpp_tests fortran_tests
python3 setup.py test
.PHONY: test_cpp
-test_cpp:
+test_cpp: cpp
cd libmuscle/cpp && $(MAKE) test
.PHONY: test_fortran
-test_fortran:
+test_fortran: fortran_tests
cd libmuscle/fortran && $(MAKE) test
.PHONY: test_scripts
test_scripts:
cd scripts && $(MAKE) test
+.PHONY: test_install
+test_install:
+ PREFIX=$(CURDIR)/libmuscle/build/test_install $(MAKE) install
+
+.PHONY: test_examples
+test_examples: test_install
+ export MUSCLE3_HOME=$(CURDIR)/libmuscle/build/test_install && $(MAKE) -C docs/source/examples test
+
+
.PHONY: install
install: all
cd libmuscle/cpp && $(MAKE) install
@@ -61,6 +81,12 @@ install: all
@echo " Linking: -L$(PREFIX)/lib -lymmsl_fortran"
@echo " -lmuscle_mpi_fortran -lymmsl -lmuscle_mpi"
@echo '* *'
+ @echo '* *'
+ @echo " You can also use pkg-config. Add $(PREFIX)/lib/pkgconfig"
+ @echo '* to your PKG_CONFIG_PATH environment variable and use module *'
+ @echo '* names libmuscle, libmuscle_mpi, libmuscle_fortran or *'
+ @echo '* libmuscle_mpi_fortran. *'
+ @echo '* *'
@echo '* If the directory you installed MUSCLE 3 in is not in your *'
@echo "* system's library search path, then you have to set *"
@echo '* LD_LIBRARY_PATH before compiling, linking or running: *'
@@ -88,12 +114,16 @@ clean:
cd libmuscle/cpp && $(MAKE) clean
cd libmuscle/fortran && $(MAKE) clean
cd scripts && $(MAKE) clean
+ cd docs/source/examples && $(MAKE) clean
+ rm -rf ./build
.PHONY: distclean
distclean:
cd libmuscle/cpp && $(MAKE) distclean
cd libmuscle/fortran && $(MAKE) distclean
cd scripts && $(MAKE) distclean
+ cd docs/source/examples && $(MAKE) clean
+ rm -rf ./build
.PHONY: fortran
fortran: cpp
diff --git a/README.rst b/README.rst
index 18fa4867..9ec89a33 100644
--- a/README.rst
+++ b/README.rst
@@ -1,12 +1,12 @@
-.. image:: https://github.com/multiscale/muscle3/raw/develop/docs/source/muscle3_logo_readme.png
+.. image:: https://github.com/multiscale/muscle3/raw/master/docs/source/muscle3_logo_readme.png
:alt: MUSCLE 3
.. image:: https://readthedocs.org/projects/muscle3/badge/?version=master
:target: https://muscle3.readthedocs.io/en/develop/?badge=master
:alt: Documentation Build Status
-.. image:: https://api.travis-ci.org/multiscale/muscle3.svg?branch=master
- :target: https://travis-ci.org/multiscale/muscle3
+.. image:: https://github.com/multiscale/muscle3/workflows/continuous_integration/badge.svg?branch=master
+ :target: https://github.com/multiscale/muscle3/actions
:alt: Build Status
.. image:: https://api.codacy.com/project/badge/Coverage/ea0c833cf1ce4e13840c6498dfe27ff8
diff --git a/VERSION b/VERSION
index 0d91a54c..9e11b32f 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.0
+0.3.1
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 31233dac..999c52c9 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,9 +1,10 @@
breathe
-grpcio==1.17.1
-grpcio-tools==1.17.1
+grpcio==1.24.3
+grpcio-tools==1.24.3
msgpack==0.6.1
netifaces
-protobuf==3.8.0
+numpy>=1.12
+protobuf==3.10.0
sphinx-fortran
typing==3.6.6
ymmsl
diff --git a/docs/source/.gitignore b/docs/source/.gitignore
index fd4c02c3..a710b7ff 100644
--- a/docs/source/.gitignore
+++ b/docs/source/.gitignore
@@ -1 +1,2 @@
/apidocs
+installing.rst
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 652540de..33e813ca 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -89,7 +89,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'examples/python/build/venv']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
@@ -107,6 +107,17 @@
breathe_default_members = ('members',)
+# -- Patch version into installation instructions --
+def patch_installation_version():
+ with open('installing.rst', 'w') as out_file:
+ with open('installing.rst.in', 'r') as in_file:
+ in_text = in_file.read()
+ out_text = in_text.replace('%%VERSION%%', release.strip())
+ out_file.write(out_text)
+
+patch_installation_version()
+
+
# -- Run doxygen manually, as readthedocs doesn't support it --
import subprocess
subprocess.call('cd ../.. ; doxygen', shell=True)
diff --git a/docs/source/cplusplus.rst b/docs/source/cplusplus.rst
index 3e60702f..6b49f09f 100644
--- a/docs/source/cplusplus.rst
+++ b/docs/source/cplusplus.rst
@@ -2,7 +2,7 @@ MUSCLE and C++
==============
This section shows how to use MUSCLE 3 from C++, based on the same
-reaction-diffusion model given in the Python tutorial.
+reaction-diffusion model as given in the Python tutorial.
`The source code for the examples in this section is here
`_.
You can also go to ``docs/source/examples/cpp`` in the source directory (that's
@@ -14,7 +14,7 @@ Building and running the examples
If you've just built and installed the C++ version of libmuscle, then you're all
set to build the examples. To do that, go into the ``docs/source/examples``
-subdirectory, and run Make:
+subdirectory, and run Make, passing the installed location of MUSCLE 3:
.. code-block:: bash
@@ -192,10 +192,8 @@ message on our ``F_INIT`` port with the initial state:
.. code-block:: cpp
auto msg = instance.receive("initial_state");
- DataConstRef data = msg.data();
- std::vector U(data.size());
- for (int i = 0; i < data.size(); ++i)
- U[i] = data[i].as();
+ auto data_ptr = msg.data().elements();
+ std::vector U(data_ptr, data_ptr + msg.data().size());
Calling the ``receive`` method on an instance yields an object of type
@@ -230,18 +228,30 @@ contain data of many different types, and that's what ``Data`` and
type.
In the code here, we don't bother with a check. Instead, we blindly assume that
-we've been sent a list of doubles. If that's not the case, an exception will be
-thrown and our program will halt. That's okay, because it means that there's
+we've been sent a 1D grid of doubles. If there is no grid, an exception will
+be thrown and our program will halt. That's okay, because it means that there's
something wrong somewhere that we need to fix. MUSCLE is designed to let you get
away with being a bit sloppy as long as things actually go right, but it will
check for problems and let you know if something goes wrong.
-If a ``DataConstRef`` contains a list of some kind, then its ``size()`` member
-function can be used to determine the length of that list. We use that to create
-a ``std::vector`` of the correct length, and then extract each item in the
-received list into the vector. Note that items in a list can be accessed through
-``data[i]``, and that each item is itself a ``DataConstRef``, now (hopefully)
-containing a ``double`` value that we can extract using ``as()``.
+A grid in MUSCLE 3 is an n-dimensional array of numbers or booleans. It has a
+shape (size in each dimension), a size (total number of elements), a storage
+order which says in which order the elements are arranged in memory, and
+optionally names for the indexes.
+
+Here, we expect a 1D grid of doubles, which is just a vector of numbers. Storage
+order is irrelevant then. We'll use the standard C++ ``std::vector`` class to
+contain our state. It has a constructor that takes a pointer to an array of
+objects of the appropriate type and the number of them, and copies those objects
+into itself. We use the :cpp:func:`DataConstRef::elements` to get a pointer to
+the elements, and :cpp:func:`DataConstRef::size` to get the number of elements,
+and that's all we need to create our state vector ``U``.
+
+Note that MUSCLE 3 will in this case check that we have the right type of
+elements (doubles), but it cannot know and therefore will not check that we're
+expecting a 1D grid. We could check that by hand by checking the length of
+``msg.data().shape()`` to see if it's 1. See the diffusion model below for an
+example.
.. code-block:: cpp
@@ -267,24 +277,24 @@ Sending messages and Data
.. code-block:: cpp
// O_F
- auto result = Data::nils(U.size());
- for (int i = 0; i < U.size(); ++i)
- result[i] = U[i];
+ auto result = Data::grid(U.data(), {U.size()}, {"x"});
instance.send("final_state", Message(t_cur, result));
Having computed our final state, we will send it to the outside world on the
-``final_state`` port. In this case, we need to send a list of doubles, which we
-first need to wrap up into a ``Data`` object. A ``Data`` object works just like
-a ``DataConstRef``, except that it isn't constant, and can thus be modified. (It
-is in fact a reference, like ``DataConstRef``, despite the name, and it has
-automatic memory management as well.)
-
-Here, we start by creating a ``Data`` containing a list of `U.size()` nil (null,
-None) values. This allocates enough space in the list for all of our doubles. We
-can then simply use ``result[i]`` to assign our double values to each list item.
-Note that there's no need to explicitly specify that this is a double, the
-compiler knows and will do the right thing.
+``final_state`` port. In this case, we need to send a 1D grid of doubles, which
+we first need to wrap up into a ``Data`` object. A ``Data`` object works just
+like a ``DataConstRef``, except that it isn't constant, and can thus be
+modified. (It is in fact a reference, like ``DataConstRef``, despite the name,
+and it has automatic memory management as well.)
+
+Here, we create a ``Data`` containing a grid. We pass it a pointer to the
+numbers in ``U``, and the shape of our grid, which is a 1-element array because
+the grid is 1-dimensional. The third argument contains the names of the indexes,
+which helps to avoid confusion over which index is x and which is y (or
+row/column, or latitude/longitude, or... you get the idea). You are not
+required to add these (and MUSCLE 3 doesn't use them), but you or someone else
+using your code will be very grateful you did at some point in the future.
With our data item constructed, we can send a ``Message`` containing the current
timestamp and the data to the ``final_state`` port. Note that there are
diff --git a/docs/source/examples/Makefile b/docs/source/examples/Makefile
index 958043aa..6a1ed60a 100644
--- a/docs/source/examples/Makefile
+++ b/docs/source/examples/Makefile
@@ -4,9 +4,29 @@ $(error MUSCLE3_HOME is not defined, use 'MUSCLE3_HOME=/path/to/muscle3 make ();
+ if (msg.data().shape().size() != 1u || msg.data().size() != U.size()) {
+ auto msg = "Received state of incorrect shape or size!";
+ instance.error_shutdown(msg);
+ throw std::runtime_error(msg);
+ }
+ std::copy_n(msg.data().elements(), msg.data().size(), U.begin());
std::vector dU(U.size());
auto lpl = laplacian(U, dx);
@@ -87,9 +88,7 @@ void diffusion(int argc, char * argv[]) {
}
// O_F
- auto data = Data::nils(U.size());
- for (std::size_t i = 0u; i < U.size(); ++i)
- data[i] = U[i];
+ auto data = Data::grid(U.data(), {U.size()}, {"x"});
instance.send("final_state_out", Message(t_cur, data));
std::cerr << "All done" << std::endl;
}
diff --git a/docs/source/examples/cpp/mc_driver.cpp b/docs/source/examples/cpp/mc_driver.cpp
index ad416d55..3f9b6dc0 100644
--- a/docs/source/examples/cpp/mc_driver.cpp
+++ b/docs/source/examples/cpp/mc_driver.cpp
@@ -89,12 +89,10 @@ void mc_driver(int argc, char * argv[]) {
std::cerr << "Entering S" << std::endl;
double t_max = 0.0;
for (int sample = 0; sample < n_samples; ++sample) {
- std::cerr << "Receiving states_in" << std::endl;
+ std::cerr << "Receiving states_in[" << sample << "]" << std::endl;
Message msg = instance.receive_with_settings("states_in", sample);
- std::cerr << "Received states_in: " << msg.data().size() << std::endl;
- std::vector U(msg.data().size());
- for (std::size_t i = 0u; i < msg.data().size(); ++i)
- U[i] = msg.data()[i].as();
+ auto data_ptr = msg.data().elements();
+ std::vector U(data_ptr, data_ptr + msg.data().size());
Us.push_back(U);
t_max = std::max(t_max, msg.timestamp());
diff --git a/docs/source/examples/cpp/reaction.cpp b/docs/source/examples/cpp/reaction.cpp
index aae2afc1..e5f52349 100644
--- a/docs/source/examples/cpp/reaction.cpp
+++ b/docs/source/examples/cpp/reaction.cpp
@@ -26,10 +26,8 @@ void reaction(int argc, char * argv[]) {
double k = instance.get_setting_as("k");
auto msg = instance.receive("initial_state");
- DataConstRef data(msg.data());
- std::vector U(data.size());
- for (int i = 0; i < data.size(); ++i)
- U[i] = data[i].as();
+ auto data_ptr = msg.data().elements();
+ std::vector U(data_ptr, data_ptr + msg.data().size());
double t_cur = msg.timestamp();
while (t_cur + dt < msg.timestamp() + t_max) {
@@ -42,9 +40,7 @@ void reaction(int argc, char * argv[]) {
}
// O_F
- auto result = Data::nils(U.size());
- for (int i = 0; i < U.size(); ++i)
- result[i] = U[i];
+ auto result = Data::grid(U.data(), {U.size()}, {"x"});
instance.send("final_state", Message(t_cur, result));
}
}
diff --git a/docs/source/examples/cpp/reaction_mpi.cpp b/docs/source/examples/cpp/reaction_mpi.cpp
index da99b0d9..92077217 100644
--- a/docs/source/examples/cpp/reaction_mpi.cpp
+++ b/docs/source/examples/cpp/reaction_mpi.cpp
@@ -39,10 +39,9 @@ void reaction(int argc, char * argv[]) {
auto msg = instance.receive("initial_state");
if (rank == root_rank) {
- DataConstRef data(msg.data());
- U_all.resize(data.size());
- for (int i = 0; i < data.size(); ++i)
- U_all[i] = data[i].as();
+ auto data_ptr = msg.data().elements();
+ U_all.resize(msg.data().size());
+ std::copy_n(data_ptr, msg.data().size(), U_all.begin());
t_cur = msg.timestamp();
t_end = t_cur + t_max;
@@ -77,9 +76,7 @@ void reaction(int argc, char * argv[]) {
root_rank, MPI_COMM_WORLD);
if (rank == root_rank) {
- auto result = Data::nils(U_all.size());
- for (int i = 0; i < U_all.size(); ++i)
- result[i] = U_all[i];
+ auto result = Data::grid(U_all.data(), {U_all.size()}, {"x"});
instance.send("final_state", Message(t_cur, result));
}
}
diff --git a/docs/source/examples/fortran/build/Makefile b/docs/source/examples/fortran/build/Makefile
index 181257f4..049d7948 100644
--- a/docs/source/examples/fortran/build/Makefile
+++ b/docs/source/examples/fortran/build/Makefile
@@ -1,8 +1,9 @@
FC ?= gfortran
-FFLAGS += -std=f2003 -g -I$(MUSCLE3_HOME)/include
+MPIFC ?= mpifort
+FFLAGS += -std=f2003 -g $(shell pkg-config --cflags libmuscle_fortran)
-MPI_LDFLAGS := -L$(MUSCLE3_HOME)/lib -lymmsl_fortran -lmuscle_mpi_fortran -lymmsl -lmuscle_mpi
-LDFLAGS := -L$(MUSCLE3_HOME)/lib -lymmsl_fortran -lmuscle_fortran -lymmsl -lmuscle
+MPI_LDFLAGS := $(shell pkg-config --libs libmuscle_mpi_fortran)
+LDFLAGS := $(shell pkg-config --libs libmuscle_fortran)
binaries := reaction diffusion mc_driver load_balancer
@@ -25,5 +26,5 @@ clean:
LD_LIBRARY_PATH=$(MUSCLE3_HOME)/lib:$(LD_LIBRARY_PATH) $(FC) $(FFLAGS) -o $@ $^ $(LDFLAGS)
%_mpi: ../%_mpi.f03
- LD_LIBRARY_PATH=$(MUSCLE3_HOME)/lib:$(LD_LIBRARY_PATH) mpi$(FC) $(FFLAGS) -o $@ $^ $(MPI_LDFLAGS)
+ LD_LIBRARY_PATH=$(MUSCLE3_HOME)/lib:$(LD_LIBRARY_PATH) $(MPIFC) $(FFLAGS) -o $@ $^ $(MPI_LDFLAGS)
diff --git a/docs/source/examples/fortran/diffusion.f03 b/docs/source/examples/fortran/diffusion.f03
index 34c47264..0f7015bc 100644
--- a/docs/source/examples/fortran/diffusion.f03
+++ b/docs/source/examples/fortran/diffusion.f03
@@ -20,7 +20,7 @@ program diffusion
type(LIBMUSCLE_Data) :: sdata
real (selected_real_kind(15)) :: t_cur, t_next, t_max, dt, x_max, dx, d
- integer (LIBMUSCLE_size) :: i, U_size, n_steps, iteration
+ integer (LIBMUSCLE_size) :: U_size, n_steps, iteration
real (selected_real_kind(15)), dimension(:), allocatable :: U, dU
real (selected_real_kind(15)), dimension(:, :), allocatable :: Us
@@ -57,12 +57,9 @@ program diffusion
do while (t_cur + dt < t_max)
print *, 't_cur: ', t_cur, 't_max: ', t_max
! O_I
- sdata = LIBMUSCLE_Data_create_nils(U_size)
- do i = 1, U_size
- call LIBMUSCLE_Data_set_item(sdata, i, U(i))
- end do
-
+ sdata = LIBMUSCLE_Data_create_grid(U, 'x')
smsg = LIBMUSCLE_Message_create(t_cur, sdata)
+ call LIBMUSCLE_Data_free(sdata)
t_next = t_cur + dt
if (t_next + dt <= t_max) then
call LIBMUSCLE_Message_set_next_timestamp(smsg, t_next)
@@ -72,15 +69,10 @@ program diffusion
! S
rmsg = LIBMUSCLE_Instance_receive(instance, 'state_in', smsg)
rdata = LIBMUSCLE_Message_get_data(rmsg)
- do i = 1, U_size
- item = LIBMUSCLE_DataConstRef_get_item(rdata, i)
- U(i) = LIBMUSCLE_DataConstRef_as_real8(item)
- call LIBMUSCLE_DataConstRef_free(item)
- end do
+ call LIBMUSCLE_DataConstRef_elements(rdata, U)
call LIBMUSCLE_DataConstRef_free(rdata)
call LIBMUSCLE_Message_free(rmsg)
call LIBMUSCLE_Message_free(smsg)
- call LIBMUSCLE_Data_free(sdata)
dU(2:U_size-1) = d * laplacian(U, dx) * dt
dU(1) = dU(2)
@@ -94,14 +86,9 @@ program diffusion
end do
! O_F
- sdata = LIBMUSCLE_Data_create_nils(U_size)
- do i = 1, U_size
- call LIBMUSCLE_Data_set_item(sdata, i, U(i))
- end do
-
+ sdata = LIBMUSCLE_Data_create_grid(U, 'x')
smsg = LIBMUSCLE_Message_create(t_cur, sdata)
call LIBMUSCLE_Instance_send(instance, 'final_state_out', smsg)
-
call LIBMUSCLE_Message_free(smsg)
call LIBMUSCLE_Data_free(sdata)
deallocate (U, dU, Us)
diff --git a/docs/source/examples/fortran/mc_driver.f03 b/docs/source/examples/fortran/mc_driver.f03
index 6ed57909..142156dc 100644
--- a/docs/source/examples/fortran/mc_driver.f03
+++ b/docs/source/examples/fortran/mc_driver.f03
@@ -102,9 +102,7 @@ program mc_driver
allocate (Us(n_samples, U_size))
end if
- do i = 1, U_size
- Us(sample, i) = LIBMUSCLE_DataConstRef_as_real8(LIBMUSCLE_DataConstRef_get_item(rdata, i))
- end do
+ call LIBMUSCLE_DataConstRef_elements(rdata, Us(sample, :))
t_max = max(t_max, LIBMUSCLE_Message_timestamp(rmsg))
call LIBMUSCLE_DataConstRef_free(rdata)
@@ -112,11 +110,7 @@ program mc_driver
end do
! calculate mean
- means = LIBMUSCLE_Data_create_nils(U_size)
-
- do i = 1, U_size
- call LIBMUSCLE_Data_set_item(means, i, sum(Us(:, i)) / n_samples)
- end do
+ means = LIBMUSCLE_Data_create_grid(sum(Us, 1) / n_samples, 'x')
smsg = LIBMUSCLE_Message_create(t_max, means)
call LIBMUSCLE_Instance_send(instance, 'mean_out', smsg)
diff --git a/docs/source/examples/fortran/reaction.f03 b/docs/source/examples/fortran/reaction.f03
index d7b6ad00..5013d748 100644
--- a/docs/source/examples/fortran/reaction.f03
+++ b/docs/source/examples/fortran/reaction.f03
@@ -14,7 +14,6 @@ program reaction
type(LIBMUSCLE_Data) :: sdata
real (selected_real_kind(15)) :: t_cur, t_max, dt, k
- integer (LIBMUSCLE_size) :: i, U_size
real (selected_real_kind(15)), dimension(:), allocatable :: U
@@ -32,14 +31,8 @@ program reaction
rmsg = LIBMUSCLE_Instance_receive(instance, 'initial_state')
rdata = LIBMUSCLE_Message_get_data(rmsg)
-
- U_size = LIBMUSCLE_DataConstRef_size(rdata)
- allocate (U(U_size))
- do i = 1, U_size
- item = LIBMUSCLE_DataConstRef_get_item(rdata, i)
- U(i) = LIBMUSCLE_DataConstRef_as_real8(item)
- call LIBMUSCLE_DataConstRef_free(item)
- end do
+ allocate (U(LIBMUSCLE_DataConstRef_size(rdata)))
+ call LIBMUSCLE_DataConstRef_elements(rdata, U)
call LIBMUSCLE_DataConstRef_free(rdata)
t_cur = LIBMUSCLE_Message_timestamp(rmsg)
@@ -55,14 +48,9 @@ program reaction
end do
! O_F
- sdata = LIBMUSCLE_Data_create_nils(U_size)
- do i = 1, U_size
- call LIBMUSCLE_Data_set_item(sdata, i, U(i))
- end do
-
+ sdata = LIBMUSCLE_Data_create_grid(U, 'x')
smsg = LIBMUSCLE_Message_create(t_cur, sdata)
call LIBMUSCLE_Instance_send(instance, 'final_state', smsg)
-
call LIBMUSCLE_Message_free(smsg)
call LIBMUSCLE_Data_free(sdata)
deallocate (U)
diff --git a/docs/source/examples/fortran/reaction_mpi.f03 b/docs/source/examples/fortran/reaction_mpi.f03
index 4dbb0977..8dcdacdc 100644
--- a/docs/source/examples/fortran/reaction_mpi.f03
+++ b/docs/source/examples/fortran/reaction_mpi.f03
@@ -42,11 +42,7 @@ program reaction_mpi
rdata = LIBMUSCLE_Message_get_data(rmsg)
U_all_size = LIBMUSCLE_DataConstRef_size(rdata)
allocate (U_all(U_all_size))
- do i = 1, U_all_size
- item = LIBMUSCLE_DataConstRef_get_item(rdata, int(i, LIBMUSCLE_size))
- U_all(i) = LIBMUSCLE_DataConstRef_as_real8(item)
- call LIBMUSCLE_DataConstRef_free(item)
- end do
+ call LIBMUSCLE_DataConstRef_elements(rdata, U_all)
call LIBMUSCLE_DataConstRef_free(rdata)
t_cur = LIBMUSCLE_Message_timestamp(rmsg)
@@ -84,11 +80,7 @@ program reaction_mpi
root_rank, MPI_COMM_WORLD, ierr)
if (rank == root_rank) then
- sdata = LIBMUSCLE_Data_create_nils(int(U_all_size, LIBMUSCLE_size))
- do i = 1, U_all_size
- call LIBMUSCLE_Data_set_item(sdata, int(i, LIBMUSCLE_size), U_all(i))
- end do
-
+ sdata = LIBMUSCLE_Data_create_grid(U_all, 'x')
smsg = LIBMUSCLE_Message_create(t_cur, sdata)
call LIBMUSCLE_Instance_send(instance, 'final_state', smsg)
diff --git a/docs/source/examples/python/build/Makefile b/docs/source/examples/python/build/Makefile
index bf19818b..f8892e12 100644
--- a/docs/source/examples/python/build/Makefile
+++ b/docs/source/examples/python/build/Makefile
@@ -1,3 +1,7 @@
+python_version := $(word 2, $(shell python3 --version))
+not_python_3_5 := $(patsubst 3.5%,,$(python_version))
+reqs := $(if $(not_python_3_5), ../requirements.txt, ../requirements_3.5.txt)
+
.PHONY: all
all: venv
@@ -9,4 +13,4 @@ clean:
venv:
python3 -m venv venv
- . venv/bin/activate && pip3 install -r ../requirements.txt
+ . venv/bin/activate && pip3 install -U pip setuptools wheel && pip3 install -r $(reqs) && pip3 install $(CURDIR)/../../../../../
diff --git a/docs/source/examples/python/diffusion.py b/docs/source/examples/python/diffusion.py
index 7eb255b2..df151bd4 100644
--- a/docs/source/examples/python/diffusion.py
+++ b/docs/source/examples/python/diffusion.py
@@ -3,7 +3,7 @@
import numpy as np
-from libmuscle import Instance, Message
+from libmuscle import Grid, Instance, Message
from ymmsl import Operator
@@ -55,14 +55,14 @@ def diffusion() -> None:
t_next = t_cur + dt
if t_next + dt > t_max:
t_next = None
- cur_state_msg = Message(t_cur, t_next, U.tolist())
+ cur_state_msg = Message(t_cur, t_next, Grid(U, ['x']))
instance.send('state_out', cur_state_msg)
# S
msg = instance.receive('state_in', default=cur_state_msg)
if msg.timestamp > t_cur + dt:
logger.warning('Received a message from the future!')
- U = np.array(msg.data)
+ np.copyto(U, msg.data.array)
dU = np.zeros_like(U)
dU[1:-1] = d * laplacian(U, dx) * dt
diff --git a/docs/source/examples/python/reaction.py b/docs/source/examples/python/reaction.py
index cc6c051e..0b65ae81 100644
--- a/docs/source/examples/python/reaction.py
+++ b/docs/source/examples/python/reaction.py
@@ -1,6 +1,6 @@
import numpy as np
-from libmuscle import Instance, Message
+from libmuscle import Grid, Instance, Message
from ymmsl import Operator
@@ -18,7 +18,7 @@ def reaction() -> None:
k = instance.get_setting('k', 'float')
msg = instance.receive('initial_state')
- U = np.array(msg.data)
+ U = msg.data.array.copy()
t_cur = msg.timestamp
while t_cur + dt < msg.timestamp + t_max:
@@ -29,7 +29,7 @@ def reaction() -> None:
t_cur += dt
# O_F
- instance.send('final_state', Message(t_cur, None, U.tolist()))
+ instance.send('final_state', Message(t_cur, None, Grid(U, ['x'])))
if __name__ == '__main__':
diff --git a/docs/source/examples/python/reaction_diffusion.py b/docs/source/examples/python/reaction_diffusion.py
index a84969aa..db469282 100644
--- a/docs/source/examples/python/reaction_diffusion.py
+++ b/docs/source/examples/python/reaction_diffusion.py
@@ -4,7 +4,7 @@
import numpy as np
-from libmuscle import Instance, Message
+from libmuscle import Grid, Instance, Message
from libmuscle.runner import run_simulation
from ymmsl import (ComputeElement, Conduit, Configuration, Model, Operator,
Settings)
@@ -24,7 +24,7 @@ def reaction() -> None:
k = instance.get_setting('k', 'float')
msg = instance.receive('initial_state')
- U = np.array(msg.data)
+ U = msg.data.array.copy()
t_cur = msg.timestamp
while t_cur + dt < msg.timestamp + t_max:
@@ -35,7 +35,7 @@ def reaction() -> None:
t_cur += dt
# O_F
- instance.send('final_state', Message(t_cur, None, U.tolist()))
+ instance.send('final_state', Message(t_cur, None, Grid(U, ['x'])))
def laplacian(Z: np.array, dx: float) -> np.array:
@@ -85,14 +85,14 @@ def diffusion() -> None:
t_next = t_cur + dt
if t_next + dt > t_max:
t_next = None
- cur_state_msg = Message(t_cur, t_next, U.tolist())
+ cur_state_msg = Message(t_cur, t_next, Grid(U, ['x']))
instance.send('state_out', cur_state_msg)
# S
msg = instance.receive('state_in', default=cur_state_msg)
if msg.timestamp > t_cur + dt:
logging.warning('Received a message from the future!')
- U = np.array(msg.data)
+ np.copyto(U, msg.data.array)
dU = np.zeros_like(U)
dU[1:-1] = d * laplacian(U, dx) * dt
diff --git a/docs/source/examples/python/reaction_diffusion_qmc.py b/docs/source/examples/python/reaction_diffusion_qmc.py
index 5d36d839..9a98e3c6 100644
--- a/docs/source/examples/python/reaction_diffusion_qmc.py
+++ b/docs/source/examples/python/reaction_diffusion_qmc.py
@@ -4,7 +4,7 @@
import numpy as np
import sobol_seq
-from libmuscle import Instance, Message
+from libmuscle import Grid, Instance, Message
from libmuscle.runner import run_simulation
from ymmsl import (ComputeElement, Conduit, Configuration, Model, Operator,
Settings)
@@ -24,7 +24,7 @@ def reaction() -> None:
k = instance.get_setting('k', 'float')
msg = instance.receive('initial_state')
- U = np.array(msg.data)
+ U = msg.data.array.copy()
t_cur = msg.timestamp
while t_cur + dt < msg.timestamp + t_max:
@@ -35,7 +35,7 @@ def reaction() -> None:
t_cur += dt
# O_F
- instance.send('final_state', Message(t_cur, None, U.tolist()))
+ instance.send('final_state', Message(t_cur, None, Grid(U, ['x'])))
def laplacian(Z: np.array, dx: float) -> np.array:
@@ -86,14 +86,14 @@ def diffusion() -> None:
t_next = t_cur + dt
if t_next + dt > t_max:
t_next = None
- cur_state_msg = Message(t_cur, t_next, U.tolist())
+ cur_state_msg = Message(t_cur, t_next, Grid(U, ['x']))
instance.send('state_out', cur_state_msg)
# S
msg = instance.receive('state_in', default=cur_state_msg)
if msg.timestamp > t_cur + dt:
logger.warning('Received a message from the future!')
- U = np.array(msg.data)
+ np.copyto(U, msg.data.array)
dU = np.zeros_like(U)
dU[1:-1] = d * laplacian(U, dx) * dt
@@ -105,7 +105,7 @@ def diffusion() -> None:
t_cur += dt
# O_F
- instance.send('final_state_out', Message(t_cur, None, U.tolist()))
+ instance.send('final_state_out', Message(t_cur, None, Grid(U, ['x'])))
def load_balancer() -> None:
@@ -206,7 +206,7 @@ def qmc_driver() -> None:
# S
for sample in range(n_samples):
msg = instance.receive_with_settings('states_in', sample)
- U = np.array(msg.data)
+ U = msg.data.array
# accumulate
if Us is None:
Us = U
diff --git a/docs/source/examples/python/requirements.txt b/docs/source/examples/python/requirements.txt
index 6ee8f812..f9218745 100644
--- a/docs/source/examples/python/requirements.txt
+++ b/docs/source/examples/python/requirements.txt
@@ -1,4 +1,3 @@
matplotlib
-muscle3
numpy
sobol_seq
diff --git a/docs/source/examples/python/requirements_3.5.txt b/docs/source/examples/python/requirements_3.5.txt
new file mode 100644
index 00000000..f5edc789
--- /dev/null
+++ b/docs/source/examples/python/requirements_3.5.txt
@@ -0,0 +1,3 @@
+matplotlib<3
+numpy
+sobol_seq
diff --git a/docs/source/examples/reaction_diffusion_cpp.sh b/docs/source/examples/reaction_diffusion_cpp.sh
index 31ff5cc0..4e421527 100755
--- a/docs/source/examples/reaction_diffusion_cpp.sh
+++ b/docs/source/examples/reaction_diffusion_cpp.sh
@@ -6,6 +6,8 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running reaction-diffusion in C++'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion.ymmsl &
diff --git a/docs/source/examples/reaction_diffusion_fortran.sh b/docs/source/examples/reaction_diffusion_fortran.sh
index 31ff5cc0..bdce14eb 100755
--- a/docs/source/examples/reaction_diffusion_fortran.sh
+++ b/docs/source/examples/reaction_diffusion_fortran.sh
@@ -6,13 +6,15 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running reaction-diffusion in Fortran'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion.ymmsl &
manager_pid=$!
export LD_LIBRARY_PATH=$MUSCLE3_HOME/lib:$LD_LIBRARY_PATH
-BINDIR=cpp/build
+BINDIR=fortran/build
$BINDIR/reaction --muscle-instance=micro >'micro.log' 2>&1 &
$BINDIR/diffusion --muscle-instance=macro >'macro.log' 2>&1 &
diff --git a/docs/source/examples/reaction_diffusion_mc_cpp.sh b/docs/source/examples/reaction_diffusion_mc_cpp.sh
index a543f819..962a921e 100755
--- a/docs/source/examples/reaction_diffusion_mc_cpp.sh
+++ b/docs/source/examples/reaction_diffusion_mc_cpp.sh
@@ -6,6 +6,8 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running MC reaction-diffusion in C++'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion_mc.ymmsl &
diff --git a/docs/source/examples/reaction_diffusion_mc_fortran.sh b/docs/source/examples/reaction_diffusion_mc_fortran.sh
index 7f8971b2..b5c3cefc 100755
--- a/docs/source/examples/reaction_diffusion_mc_fortran.sh
+++ b/docs/source/examples/reaction_diffusion_mc_fortran.sh
@@ -6,6 +6,8 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running MC reaction-diffusion in Fortran'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion_mc.ymmsl &
diff --git a/docs/source/examples/reaction_diffusion_mpi_cpp.sh b/docs/source/examples/reaction_diffusion_mpi_cpp.sh
index a4f9cd9b..dcc18da1 100755
--- a/docs/source/examples/reaction_diffusion_mpi_cpp.sh
+++ b/docs/source/examples/reaction_diffusion_mpi_cpp.sh
@@ -6,6 +6,8 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running reaction-diffusion with MPI in C++'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion.ymmsl &
diff --git a/docs/source/examples/reaction_diffusion_mpi_fortran.sh b/docs/source/examples/reaction_diffusion_mpi_fortran.sh
index 0e707c5f..acdd6566 100755
--- a/docs/source/examples/reaction_diffusion_mpi_fortran.sh
+++ b/docs/source/examples/reaction_diffusion_mpi_fortran.sh
@@ -6,6 +6,8 @@ if [ -z "$MUSCLE3_HOME" ] ; then
exit 1
fi
+echo 'Running reaction-diffusion MPI in Fortran'
+
. python/build/venv/bin/activate
muscle_manager reaction_diffusion.ymmsl &
diff --git a/docs/source/examples/reaction_diffusion_python.sh b/docs/source/examples/reaction_diffusion_python.sh
index facded6c..843dbf11 100755
--- a/docs/source/examples/reaction_diffusion_python.sh
+++ b/docs/source/examples/reaction_diffusion_python.sh
@@ -7,6 +7,8 @@ manager_pid=$!
BINDIR=python
+echo 'Running reaction-diffusion in Python'
+
python $BINDIR/reaction.py --muscle-instance=micro >'micro.log' 2>&1 &
python $BINDIR/diffusion.py --muscle-instance=macro >'macro.log' 2>&1 &
diff --git a/docs/source/examples/reaction_diffusion_python_cpp.sh b/docs/source/examples/reaction_diffusion_python_cpp.sh
new file mode 100755
index 00000000..f04c0276
--- /dev/null
+++ b/docs/source/examples/reaction_diffusion_python_cpp.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if [ -z "$MUSCLE3_HOME" ] ; then
+ echo 'Error: MUSCLE3_HOME is not set.'
+ echo "Use 'MUSCLE3_HOME=/path/to/muscle3 $0' to run the example"
+ exit 1
+fi
+
+echo 'Running reaction-diffusion in Python and C++'
+
+. python/build/venv/bin/activate
+muscle_manager reaction_diffusion.ymmsl &
+
+manager_pid=$!
+
+export LD_LIBRARY_PATH=$MUSCLE3_HOME/lib:$LD_LIBRARY_PATH
+
+python python/reaction.py --muscle-instance=micro >'micro.log' 2>&1 &
+cpp/build/diffusion --muscle-instance=macro >'macro.log' 2>&1 &
+
+touch muscle3_manager.log
+tail -f muscle3_manager.log --pid=${manager_pid}
+
+wait
+
diff --git a/docs/source/examples/reaction_diffusion_python_fortran.sh b/docs/source/examples/reaction_diffusion_python_fortran.sh
new file mode 100755
index 00000000..5aef427a
--- /dev/null
+++ b/docs/source/examples/reaction_diffusion_python_fortran.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+if [ -z "$MUSCLE3_HOME" ] ; then
+ echo 'Error: MUSCLE3_HOME is not set.'
+ echo "Use 'MUSCLE3_HOME=/path/to/muscle3 $0' to run the example"
+ exit 1
+fi
+
+echo 'Running reaction-diffusion in Python and Fortran'
+
+. python/build/venv/bin/activate
+muscle_manager reaction_diffusion.ymmsl &
+
+manager_pid=$!
+
+export LD_LIBRARY_PATH=$MUSCLE3_HOME/lib:$LD_LIBRARY_PATH
+
+python python/reaction.py --muscle-instance=micro >'micro.log' 2>&1 &
+fortran/build/diffusion --muscle-instance=macro >'macro.log' 2>&1 &
+
+touch muscle3_manager.log
+tail -f muscle3_manager.log --pid=${manager_pid}
+
+wait
+
diff --git a/docs/source/fortran.rst b/docs/source/fortran.rst
index e78d4ac3..cd817464 100644
--- a/docs/source/fortran.rst
+++ b/docs/source/fortran.rst
@@ -154,7 +154,6 @@ Variables
type(LIBMUSCLE_Data) :: sdata
real (selected_real_kind(15)) :: t_cur, t_max, dt, k
- integer (LIBMUSCLE_size) :: i, U_size
real (selected_real_kind(15)), dimension(:), allocatable :: U
@@ -172,14 +171,7 @@ may want to use.
Eagle-eyed readers will have noticed that ``dx`` and ``x_max`` are missing. That
is because we'll derive the size of the state vector of the model (``U``) from
-the state we receive, rather than from the configuration. To facilitate that,
-the index variable ``i`` and the size ``U_size`` are now of integer kind
-``LIBMUSCLE_size``. Both the default integer kind and ``LIBMUSCLE_size`` are
-easily large enough to accomodate any reasonable state size, so this doesn't
-matter much, but MUSCLE 3 uses the ``LIBMUSCLE_size`` kind for sizes and so
-we'll use it here too. If that doesn't work for your model, then you can use the
-standard Fortran ``int()`` function to convert between the different kinds.
-
+the state we receive, rather than from the configuration.
Creating an Instance
````````````````````
@@ -242,21 +234,15 @@ message on our ``F_INIT`` port with the initial state:
rmsg = LIBMUSCLE_Instance_receive(instance, 'initial_state')
rdata = LIBMUSCLE_Message_get_data(rmsg)
-
- U_size = LIBMUSCLE_DataConstRef_size(rdata)
- allocate (U(U_size))
- do i = 1, U_size
- item = LIBMUSCLE_DataConstRef_get_item(rdata, i)
- U(i) = LIBMUSCLE_DataConstRef_as_real8(item)
- call LIBMUSCLE_DataConstRef_free(item)
- end do
+ allocate (U(LIBMUSCLE_DataConstRef_size(rdata)))
+ call LIBMUSCLE_DataConstRef_elements(rdata, U)
call LIBMUSCLE_DataConstRef_free(rdata)
Calling the :f:func:`LIBMUSCLE_Instance_receive` function with an instance and a
port name yields an object of type :f:type:`LIBMUSCLE_Message` containing the
received message. As always when using MUSCLE from Fortran, we have to remember
-to free the returned Message object when we are done with it. That's not yet
+to free the returned Message object when we are done with it. That's not yet
the case though, because we still need to get the received data out. In Python,
Message is a very simple class with several public members. In Fortran, objects
are always accessed and manipulated through LIBMUSCLE functions, in this case
@@ -265,36 +251,35 @@ object. Again, since MUSCLE gave us an object, we have to remember to free it
when we're done.
First though, we'll use the data to initialise our state. We are going to assume
-that we'll receive a list of numbers, just like in the equivalent examples in
+that we'll receive a 1D grid of numbers, just like in the equivalent examples in
the other supported languages. :f:func:`LIBMUSCLE_DataConstRef_size` will return
-the size of the list that we received (or print an error and stop if we received
-something else that doesn't have a size, see below for handling errors
+the size of the array that we received (or print an error and stop if we
+received something else that doesn't have a size, see below for handling errors
differently). We use that to allocate the ``U`` array to the same size as the
-received state, and then we copy the values from the received list into ``U``.
-To get the values out, we use :f:func:`LIBMUSCLE_DataConstRef_get_item` to get a
-DataConstRef object containing the item, and then we use
-:f:func:`LIBMUSCLE_DataConstRef_as_real8` to extract a double precision real
-value from that. And then, of course, we need to free the returned ``item``
-object.
+received state, and then we copy the elements of the array into ``U`` using
+:f:func:`LIBMUSCLE_DataConstRef_elements`. We can then free the ``rdata``
+object, since we don't need it any longer. We'll hold on to the ``rmsg`` a bit
+longer, since we'll need it later.
If all this freeing objects is starting to sound tedious, that's because it is,
and it's why more modern languages like Python and C++ do this for you.
Unfortunately, for Fortran, it has to be done manually.
-Note that item indices start at 1, as usual in Fortran. MUSCLE 3 follows the
-language in which you're using it and automatically translates, so if this list
-was sent from Python or C++, then received item 1 corresponds to sent item 0.
+Note that indices for the received array start at 1, as usual in Fortran. MUSCLE
+3 follows the language in which you're using it and automatically translates, so
+if this grid was sent from Python or C++, then received item 1 corresponds to
+sent item 0.
If you have a DataConstRef object, then you can check which kind of value it
-contains, e.g. using :f:func:`LIBMUSCLE_DataConstRef_is_a_real8`. Here, we don't
-bother with a check. Instead, we blindly assume that we've been sent a list of
-doubles. If that's not the case, an error will be printed and our program will
-halt. That's okay, because it means that there's something wrong somewhere that
-we need to fix. MUSCLE is designed to let you get away with being a bit sloppy
-as long as things actually go right, but it will check for problems and let you
-know if something goes wrong. If you want to make a submodel or component that
-can handle different kinds of messages, then these inspection functions will
-help you do so however.
+contains, e.g. using :f:func:`LIBMUSCLE_DataConstRef_is_a_grid_of_real8`. Here,
+we don't bother with a check. Instead, we blindly assume that we've been sent a
+1D grid of doubles. If that's not the case, an error will be printed and our
+program will halt. That's okay, because it means that there's something wrong
+somewhere that we need to fix. MUSCLE is designed to let you get away with being
+a bit sloppy as long as things actually go right, but it will check for problems
+and let you know if something goes wrong. If you want to make a submodel or
+component that can handle different kinds of messages, then these inspection
+functions will help you do so however.
.. code-block:: fortran
@@ -326,14 +311,9 @@ Sending messages and Data
.. code-block:: fortran
! O_F
- sdata = LIBMUSCLE_Data_create_nils(U_size)
- do i = 1, U_size
- call LIBMUSCLE_Data_set_item(sdata, i, U(i))
- end do
-
+ sdata = LIBMUSCLE_Data_create_grid(U, 'x')
smsg = LIBMUSCLE_Message_create(t_cur, sdata)
call LIBMUSCLE_Instance_send(instance, 'final_state', smsg)
-
call LIBMUSCLE_Message_free(smsg)
call LIBMUSCLE_Data_free(sdata)
deallocate (U)
@@ -343,24 +323,20 @@ Sending messages and Data
Having computed our final state, we will send it to the outside world on the
-``final_state`` port. In this case, we need to send a list of doubles, which we
-first need to wrap up into a ``Data`` object. A ``Data`` object works just like
-a ``DataConstRef``, except that it isn't constant, and can thus be modified.
-
-Here, we start by creating a ``Data`` containing a list of ``U_size`` nil (null,
-None) values. This allocates enough space in the list for all of our reals. We
-can then use :f:func:`LIBMUSCLE_Data_set_item` to assign the value to each list
-item. Note that there's no need to explicitly specify the type here; it will be
-inferred from the type of the value that we're passing. Indexes range from 1 to
-the size of the array, as usual in Fortran.
-
-Having put our data into the list, we can then put the list into a new
-:f:type:`LIBMUSCLE_Message` object, and call the
+``final_state`` port. In this case, we need to send a vector of doubles, which
+we first need to wrap up into a ``Data`` object. A ``Data`` object works just
+like a ``DataConstRef``, except that it isn't constant, and can thus be
+modified. We do this by creating a ``Data`` object containing a grid value, with
+the array ``U`` and the index name ``x``.
+
+Having put our data into the ``Data`` object is a grid, we can then put the grid
+into a new :f:type:`LIBMUSCLE_Message` object, and call the
:f:func:`LIBMUSCLE_Instance_send` function to send it. Finally, we free the
Message and Data objects that we created, and deallocate the state as in the
-original non-MUSCLE version. That concludes the reuse loop. When we're done
-running the model, the reuse loop will finish, and we can free our Instance
-object before we quit.
+original non-MUSCLE version.
+
+That concludes the reuse loop. When we're done running the model, the reuse loop
+will finish, and we can free our Instance object before we quit.
:f:type:`LIBMUSCLE_Data` is quite versatile, and makes it easier to send data of
various types between submodels. Here are some other examples of creating
@@ -406,11 +382,7 @@ your model!):
As you can see, sending complex data types with MUSCLE is a bit more difficult
-in Fortran than in Python, but it is not too burdensome. One thing that's still
-missing is sending and receiving multidimensional arrays of numbers. We hope to
-add that in a future version; for now you'll need to put them into a list of
-numbers manually.
-
+in Fortran than in Python, but it is not too burdensome.
Handling errors
```````````````
diff --git a/docs/source/fortran_api.rst b/docs/source/fortran_api.rst
index 9487fc87..febc3bb1 100644
--- a/docs/source/fortran_api.rst
+++ b/docs/source/fortran_api.rst
@@ -162,6 +162,44 @@ LIBMUSCLE_Data
:r obj: The new Data object.
:rtype obj: LIBMUSCLE_Data
+.. f:function:: LIBMUSCLE_Data_create_grid(data_array)
+
+ Creates a Data object containing a grid (array).
+
+ The argument must be an array of type ``logical`` and default kind,
+ an array of type ``integer`` and kind ``LIBMUSCLE_int4`` or
+ ``LIBMUSCLE_int8``, or an array of type ``real`` and kind
+ ``LIBMUSCLE_real4`` or kind ``LIBMUSCLE_real8``.
+
+ Grids created with this function have no index names.
+
+ :p array data_array: The array of data to represent.
+ :r obj: The new Data object.
+ :rtype obj: LIBMUSCLE_Data
+
+.. f:function:: LIBMUSCLE_Data_create_grid(data_array, index_name, ...)
+
+ Creates a Data object containing a grid (array).
+
+ The ``data_array`` argument must be an array of type ``logical`` and
+ default kind, an array of type ``integer`` and kind ``LIBMUSCLE_int4``
+ or ``LIBMUSCLE_int8``, or an array of type ``real`` and kind
+ ``LIBMUSCLE_real4`` or kind ``LIBMUSCLE_real8``.
+
+ If an ``n``-dimensional array is passed as the first argument, then
+ there must be ``n`` additional arguments of type ``character``, giving
+ the names of the indexes in order. For instance, if your 2D array
+ represents a table and you index it ``data_array(row, column)`` then
+ ``"row"`` and ``"column"`` would be reasonable index names here. Note
+ that MUSCLE 3 does not use these names, they are here to make it
+ easier to understand the message on the receiver side, or if it is
+ saved and analysed later.
+
+ :p array data_array: The array of data to represent.
+ :p character index_name: The names of the grid's indexes.
+ :r obj: The new Data object.
+ :rtype obj: LIBMUSCLE_Data
+
.. f:function:: LIBMUSCLE_Data_create_dict()
Creates a Data object containing an empty dictionary.
@@ -371,6 +409,50 @@ LIBMUSCLE_Data
:r is: True if the object contains a list.
:rtype is: logical
+.. f:function:: LIBMUSCLE_Data_is_a_grid_of_logical(self)
+
+ Determine whether the Data object contains a grid of logical values.
+
+ :p LIBMUSCLE_Data self: The Data object to inspect.
+ :r is: True if the object contains a grid of logical.
+ :rtype is: logical
+
+.. f:function:: LIBMUSCLE_Data_is_a_grid_of_int4(self)
+
+ Determine whether the Data object contains a grid of integer values
+ of kind ``LIBMUSCLE_int4``.
+
+ :p LIBMUSCLE_Data self: The Data object to inspect.
+ :r is: True if the object contains a grid of int4.
+ :rtype is: logical
+
+.. f:function:: LIBMUSCLE_Data_is_a_grid_of_int8(self)
+
+ Determine whether the Data object contains a grid of integer values
+ of kind ``LIBMUSCLE_int8``.
+
+ :p LIBMUSCLE_Data self: The Data object to inspect.
+ :r is: True if the object contains a grid of int8.
+ :rtype is: logical
+
+.. f:function:: LIBMUSCLE_Data_is_a_grid_of_real4(self)
+
+ Determine whether the Data object contains a grid of real values
+ of kind ``LIBMUSCLE_real4``.
+
+ :p LIBMUSCLE_Data self: The Data object to inspect.
+ :r is: True if the object contains a grid of real4.
+ :rtype is: logical
+
+.. f:function:: LIBMUSCLE_Data_is_a_grid_of_real8(self)
+
+ Determine whether the Data object contains a grid of real8 values
+ of kind ``LIBMUSCLE_real8``.
+
+ :p LIBMUSCLE_Data self: The Data object to inspect.
+ :r is: True if the object contains a grid of real8.
+ :rtype is: logical
+
.. f:function:: LIBMUSCLE_Data_is_a_byte_array(self)
Determine whether the Data object contains a byte array value.
@@ -1080,6 +1162,84 @@ LIBMUSCLE_Data
:r value: The value at the given index
:rtype value: LIBMUSCLE_Data
+.. f:function:: LIBMUSCLE_Data_num_dims(self, err_code, err_msg)
+
+ Get the number of dimensions of a grid-valued Data object.
+
+ This function is only valid for Data objects containing a grid. You
+ can use :f:func:`LIBMUSCLE_Data_is_a_grid_of_logical` and similar
+ functions to check that it is a grid. If the Data object does not
+ contain a grid, ``err_code`` will be set to
+ ``LIBMUSCLE_runtime_error``.
+
+ :p LIBMUSCLE_Data self: The Data object to get the number of
+ dimensions for.
+ :p integer err_code: An error code output (optional).
+ :p character err_msg: An error message output (allocatable, optional).
+ :r num_dims: The number of dimensions
+ :rtype num_dims: integer (LIBMUSCLE_size)
+
+.. f:subroutine:: LIBMUSCLE_Data_shape(self, shp, err_code, err_msg)
+
+ Get the shape of the array of a grid-valued Data object.
+
+ The array passed to receive the shape must be one-dimensional, and
+ at least of length `n`, where `n` is the number of dimensions of
+ the grid.
+
+ This function is only valid for Data objects containing a grid. You
+ can use :f:func:`LIBMUSCLE_Data_is_a_grid_of_logical` and similar
+ functions to check that it is a grid. If the Data object does not
+ contain a grid, ``err_code`` will be set to
+ ``LIBMUSCLE_runtime_error``.
+
+ :p LIBMUSCLE_Data self: The data object to get the shape of.
+ :p integer shp: A 1D array of integer (LIBMUSCLE_size) to put the
+ shape into (intent (out)).
+ :p integer err_code: An error code output (optional).
+ :p character err_msg: An error message output (allocatable, optional).
+
+.. f:function:: LIBMUSCLE_Data_has_indexes(self, err_code, err_msg)
+
+ Check whether a grid has index names.
+
+ Returns ``.true.`` if the grid has index names, ``.false.`` otherwise.
+
+ This function is only valid for Data objects containing a grid. You
+ can use :f:func:`LIBMUSCLE_Data_is_a_grid_of_logical` and similar
+ functions to check that it is a grid. If the Data object does not
+ contain a grid, ``err_code`` will be set to
+ ``LIBMUSCLE_runtime_error``.
+
+ :p LIBMUSCLE_Data self: The data object to check for indexes.
+ :p integer err_code: An error code output (optional).
+ :p character err_msg: An error message output (allocatable, optional).
+ :r has_indexes: Whether there are indexes.
+ :rtype has_indexes: logical
+
+.. f:function:: LIBMUSCLE_Data_index(self, i, err_code, err_msg)
+
+ Return the name of the i'th index.
+
+ The value of ``i`` ranges from 1 to the number of dimensions.
+
+ This function is only valid for Data objects containing a grid. You
+ can use :f:func:`LIBMUSCLE_Data_is_a_grid_of_logical` and similar
+ functions to check that it is a grid. If the Data object does not
+ contain a grid, ``err_code`` will be set to
+ ``LIBMUSCLE_runtime_error``. If the index is zero, negative, or
+ larger than the number of dimensions, ``err_code`` will be set to
+ ``LIBMUSCLE_out_of_range``.
+
+ :p LIBMUSCLE_Data self: The data object to get the index of.
+ :p integer i: The index of the index to get the name of (LIBMUSCLE_size).
+ :p integer err_code: An error code output (optional).
+ :p character err_msg: An error message output (allocatable, optional).
+ :r index_name: The name of the index (allocatable)
+ :rtype index_name: logical
+
+
+
LIBMUSCLE_Message
`````````````````
.. f:type:: LIBMUSCLE_Message
diff --git a/docs/source/installing.rst b/docs/source/installing.rst.in
similarity index 53%
rename from docs/source/installing.rst
rename to docs/source/installing.rst.in
index a4bc2138..e2173f25 100644
--- a/docs/source/installing.rst
+++ b/docs/source/installing.rst.in
@@ -10,8 +10,7 @@ Python
Installing MUSCLE 3 on Python will install all the Python-based components of
the system, i.e. the Python version of libmuscle, the YMMSL Python library, and
-the MUSCLE Manager. This requires Python 3.5, 3.6 or 3.7; 3.8 may work but is
-untested.
+the MUSCLE Manager. This requires at least Python 3.5.
MUSCLE 3 is on PyPI as an ordinary Python package, so it can be installed via
Pip in the usual way. It's normally a good idea to make a virtual environment
@@ -21,18 +20,30 @@ Pip in the usual way. It's normally a good idea to make a virtual environment
~$ python3 -m venv muscle3_venv
~$ . muscle3_venv/bin/activate
+ (muscle3_venv)~$ pip install -U pip setuptools wheel
(muscle3_venv)~$ pip install muscle3
This will create a Python virtualenv in a directory named ``muscle3_venv`` in
-your home directory, activate it, and then install MUSCLE 3 inside of it. You
-can then use MUSCLE 3 whenever you have the virtualenv activated. This will also
-install the Python YMMSL library, and any required dependencies.
+your home directory, and then activate it. This means that when you run Python,
+it will use the version of Python in the virtual environment, and see the
+packages you have installed there.
+
+Next, we upgrade ``pip``, the Python package installer (most systems have an old
+version, and old versions sometimes give problems), ``setuptools`` (same thing)
+and we install ``wheel`` (which can cause packages to fail to install if it's
+not there).
+
+Having made a good environment, we can then install MUSCLE 3 inside of it. Once
+that's done, you can use MUSCLE 3 whenever you have the virtualenv activated.
+This will also install the Python YMMSL library, and any required dependencies.
You can also install MUSCLE 3 without a virtualenv if your system allows that.
The advantage of virtual environments is that you can keep different programs
separate, and reduce the chance of library version mismatches. On the other
-hand, not having to activate the virtual environment saves you a step.
+hand, not having to activate the virtual environment saves you a step. If you
+get any error messages, try upgrading pip, setuptools and wheel as shown above,
+and then try again.
If you want to install the Python YMMSL library without installing MUSCLE 3,
then you can use
@@ -53,17 +64,21 @@ slightly easier than) installing most C++ libraries.
Prerequisites
`````````````
-To build libmuscle, we're going to need some tools. In particular, we need a C++
-compiler and GNU make. MUSCLE 3 uses C++14, so you need at least g++ 4.9.3.
-Clang is expected to work, but that's not been tested, nor has any other
-compiler. If you want to try, go right ahead, we'd love to have feedback on
-this. Building has been tested with gmake 3.82 and 4.1.
+To build libmuscle, we're going to need some tools. In particular, we need a
+C++ compiler and GNU make. MUSCLE 3 uses C++14, so you need at least g++ 4.9.3.
+Clang is expected to work, but that's not been tested. Using the Intel
+toolchain currently does not work, due to issues with building gRPC with it
+(but see below for information about building submodels with the Intel tools).
+Other compilers have not been tested. If you want to try, go right ahead, we'd
+love to have feedback on this. Building has been tested with gmake 3.82 and
+4.1.
If you're doing C++ development on a reasonably modern Linux, then you probably
already have a suitable compiler installed. If not, on a Debian (or Ubuntu)
-based system, ``sudo apt-get install build-essential`` should get you set up. On
-a cluster, there is usually a ``module load g++`` or similar command available
-that sets you up with g++ and associated tools. The exact command will vary from
+based system, ``sudo apt-get install build-essential cmake gfortran pkg-config
+wget`` should get you set up. On a cluster, there is usually a ``module load
+g++`` or similar command available that sets you up with g++ and associated
+tools, and similar for a Fortran compiler. The exact command will vary from
machine to machine, so consult the documentation for your cluster and/or ask the
helpdesk.
@@ -84,18 +99,22 @@ not, MUSCLE 3 will **download and install them automatically**.
The dependencies are:
- c-ares 1.11.0 or later
-- gRPC 1.17.1 or later
+- gRPC 1.24.3
- MessagePack 3.2.0 or later
- OpenSSL 1.0.2 or later
-- Protobuf 3.7.1 or later
+- Protobuf 3.10.0
- zlib 1.2.x
If your model uses any of these dependencies directly, then it's best to install
that dependency on your system, either via the package manager or from source,
-and then link both your library and MUSCLE 3 to the dependency. This avoids
-having two different versions around and active at the same time. Otherwise,
-it's easier to rely on the automatic installation.
+and then link both your library and MUSCLE 3 to the dependency. (See below for
+how t opoint the build to your installation.) This avoids having two different
+versions around and active at the same time. Otherwise, it's easier to rely on
+the automatic installation. Note that the gRPC and Protobuf dependencies are
+exact; getting them to install and work correctly on all systems is
+unfortunately hard enough already if you limit yourself to a single version, so
+that is what we do.
Downloading MUSCLE 3
````````````````````
@@ -108,9 +127,9 @@ archive and enter the main directory:
~$ mkdir muscle3_source
~$ cd muscle3_source
- ~/muscle3_source$ wget https://github.com/multiscale/muscle3/archive/0.2.0/muscle3-0.2.0.tar.gz
- ~/muscle3_source$ tar xf muscle3-0.2.0.tar.gz
- ~/muscle3_source$ cd muscle3-0.2.0
+ ~/muscle3_source$ wget https://github.com/multiscale/muscle3/archive/%%VERSION%%/muscle3-%%VERSION%%.tar.gz
+ ~/muscle3_source$ tar xf muscle3-%%VERSION%%.tar.gz
+ ~/muscle3_source$ cd muscle3-%%VERSION%%
Of course, you can put the source anywhere you like.
@@ -123,7 +142,7 @@ The basic command for building MUSCLE 3 is:
.. code-block:: bash
- ~/muscle3_source/muscle3-0.2.0$ make
+ ~/muscle3_source/muscle3-%%VERSION%%$ make
There are a few options that can be added by setting them as environment
@@ -154,29 +173,78 @@ TAR=
This overrides the command used to unpack dependencies, which by default is
``tar``.
+protobuf_ROOT=
+ Also look in the given directory when detecting the ProtoBuf library. This
+ should be the top of the installation tree, so it will have `include/`,
+ `lib/` and `bin/` subdirectories.
+
+grpc_ROOT=
+ Also look in the given directory when detecting the gRPC library.
+
+msgpack_ROOT=
+ Also look in the given directory when detecting the MsgPack library.
+
+googletest_ROOT=
+ Also look in the given directory when detecting the GoogleTest library.
+
+c-ares_ROOT=
+ Also look in the given directory when detecting the c-ares library.
+
+zlib_ROOT=
+ Also look in the given directory when detecting zlib.
+
+openssl_ROOT=
+ Also look in the given directory when detecting OpenSSL.
+
As an example, to build libmuscle with MPI support, and using 2 cores, you would
do:
.. code-block:: bash
- ~/muscle3_source/muscle3-0.2.0$ MUSCLE_ENABLE_MPI=1 NCORES=2 make
+ ~/muscle3_source/muscle3-%%VERSION%%$ MUSCLE_ENABLE_MPI=1 NCORES=2 make
This will take ten minutes or so (including building the dependencies),
depending on the speed of your machine.
+
+Getting help
+````````````
+
+The plan is for this to always magically work, but operating systems being as
+diverse as they are (especially on HPC machines), it's possible that the build
+will fail. In that case, have a look at the output to see if you can identify
+an error message, and then go to
+`the MUSCLE 3 issues on GitHub `_
+to see if the problem has been reported already, and if there's a work-around.
+
+If not, please make a new issue with a description of the problem (preferably
+mention the error in the issue title, so that others can find it), and attach a
+log of the build. You can make a build log using:
+
+.. code-block:: bash
+
+ ~/muscle3_source/muscle3-%%VERSION%%$ make distclean
+ ~/muscle3_source/muscle3-%%VERSION%%$ make >make.log 2>&1
+
+
+This will produce a file named ``make.log`` with the build output in it. To
+attach it to a GitHub issue, drag it into the text box from your file manager.
+
+
Installing libmuscle C++
````````````````````````
-Finally, we need to install MUSCLE 3. We recommend installing it into a
-subdirectory of your home directory for now, as opposed to ``/usr/local/bin`` or
-something similar (although ``/opt/muscle3`` would be okay), since there is no
-uninstall command yet that will cleanly remove it. That goes like this:
+Once MUSCLE 3 has been compiled, we need to install it. We recommend installing
+it into a subdirectory of your home directory for now, as opposed to
+``/usr/local/bin`` or something similar (although ``/opt/muscle3`` would be
+okay), since there is no uninstall command yet that will cleanly remove it. That
+goes like this:
.. code-block:: bash
- ~/muscle3_source/muscle3-0.2.0$ PREFIX=~/muscle3 make install
+ ~/muscle3_source/muscle3-%%VERSION%%$ PREFIX=~/muscle3 make install
This command will install the C++ version of MUSCLE 3 into the directory
@@ -231,6 +299,14 @@ to the command line, or for MPI compute elements:
-L/lib -lymmsl -lmuscle_mpi
+You're most likely linking dynamically, but ``libmuscle`` does install static
+libraries in case you need them. If you link statically, then you must add the
+``-pthread`` option, as ``libmuscle`` uses background threads for communication.
+
+MUSCLE 3 also supports ``pkg-config``. To use ``pkg-config``, add
+``/lib/pkgconfig`` to your ``PKG_CONFIG_PATH`` and use the module names
+``libmuscle`` or ``libmuscle_mpi``.
+
There's one more thing: the directory that you've installed MUSCLE into is
probably not in your system's library search path, and as a result the dynamic
linker won't be able to find the libraries when you run your program. In order
@@ -246,6 +322,35 @@ If you have just installed MUSCLE 3, then the above bits are currently on your
screen, with ```` filled out already, so you can just copy-paste them
from there.
+Intel C++ compiler
+``````````````````
+
+Compiling MUSCLE 3 with the Intel compiler is currently not supported. One issue
+here is that gRPC does not compile with the Intel compiler, and it's a required
+dependency. If you're willing and able to do some manual work, you may be able
+to manually install gRPC using the GNU toolchain, and then compile MUSCLE 3
+with the Intel compiler, which may or may not work (if you encounter any
+problems, please make an issue on GitHub).
+
+However, in a typical multiscale simulation, only a small amount of time is
+spent communicating through MUSCLE 3. Your submodels will spend most of their
+time either computing or waiting. Therefore, it helps to compile the submodels
+with the Intel compiler for extra performance, but using the Intel compiler for
+MUSCLE 3 doesn't add much anyway.
+
+To compile your submodels with the Intel compiler, first use the GNU compiler
+to install MUSCLE 3. Then, switch to the Intel compiler, and use it to compile
+and link it to MUSCLE 3 as described above. The compilers are link-compatible,
+so this should work. (See below if your model is written in Fortran, you need
+to do it slightly differently there.)
+
+Note that since the Intel toolchain is proprietary, continuous testing cannot be
+done easily, and support for it is a bit experimental still. Nevertheless, the
+Intel tools are widely used in computational science, so we very much want them
+to work. If you encounter a problem, please make an issue on GitHub and we'll
+try to figure out a solution.
+
+
Fortran
-------
@@ -290,6 +395,10 @@ the libmuscle library instead:
-L/lib -lymmsl_fortran -lmuscle_mpi_fortran -lymmsl -lmuscle_mpi
+MUSCLE 3 also supports ``pkg-config``. To use ``pkg-config``, add
+``/lib/pkgconfig`` to your ``PKG_CONFIG_PATH`` and use the module names
+``libmuscle_fortran`` or ``libmuscle_mpi_fortran``.
+
There's one more thing: the directory that you've installed MUSCLE into is
probably not in your system's library search path, and as a result the dynamic
linker won't be able to find the libraries when you run your program. In order
@@ -305,3 +414,25 @@ If you have just installed MUSCLE 3, then the above bits are currently on your
screen, with ```` filled out already, so you can just copy-paste them
from there.
+Intel Fortran compiler
+``````````````````````
+
+MUSCLE 3 cannot currently be compiled with the Intel toolchain (see above under
+C++ for details). You can however compile your submodel with the Intel compiler
+and link it to the GNU-compiled MUSCLE 3 library, because the compilers are
+almost compatible. The one issue is that the ``.mod`` files created by the GNU
+compiler (and installed when you install ``libmuscle``) cannot be read by the
+Intel compiler.
+
+To solve this, you need to use the corresponding ``.f03`` files instead. These
+are installed by in ``/include``, and are called ``libmuscle.f03``,
+``ymmsl.f03``, and ``libmuscle_mpi.f03``. You compile these as you would any
+other source file in your submodel, and then link them with rest of the submodel
+and the shared library as described above.
+
+Note that since the Intel toolchain is proprietary, continuous testing cannot be
+done easily, and support for it is a bit experimental still. Nevertheless, the
+Intel tools are widely used in computational science, so we very much want them
+to work. If you encounter a problem, please make an issue on GitHub and we'll
+try to figure out a solution.
+
diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst
index bcfc7608..67b71c0a 100644
--- a/docs/source/tutorial.rst
+++ b/docs/source/tutorial.rst
@@ -69,7 +69,7 @@ Importing headers
import numpy as np
- from libmuscle import Instance, Message
+ from libmuscle import Grid, Instance, Message
from libmuscle.runner import run_simulation
from ymmsl import (ComputeElement, Conduit, Configuration, Model, Operator,
Settings)
@@ -152,7 +152,7 @@ Initialisation: Settings and receiving messages
k = instance.get_setting('k', 'float')
msg = instance.receive('initial_state')
- U = np.array(msg.data)
+ U = msg.data.array.copy()
t_cur = msg.timestamp
@@ -175,11 +175,18 @@ be received, not sent, so that declaration makes ``initial_state`` a receiving
port.
The message that we receive contains several bits of information. Here, we are
-interested in the ``data`` attribute, which we assume to be an array of floats
-containing our initial state, which we'll call ``U``. We'll initialise our
-simulation time to the time at which that state is valid, which is contained in
-the ``timestamp`` attribute. This is a double-precision float containing the
-number of simulated (not wall-clock) seconds since the whole simulation started.
+interested in the ``data`` attribute, which we assume to be a grid of floats
+containing our initial state, which we'll call ``U``. The ``msg.data`` attribute
+holds an object of type :class:`libmuscle.Grid`, which holds a read-only NumPy
+array and optionally a list of index names. Here, we take the array and make a
+copy of it for ``U``, so that we can modify ``U`` in our upcoming state update.
+Without calling ``.copy()``, ``U`` would end up pointing to the same read-only
+array, and we'd get an error message if we tried to modify it.
+
+Finally, we'll initialise our simulation time to the time at which that state is
+valid, which is contained in the ``timestamp`` attribute. This is a
+double-precision float containing the number of simulated (not wall-clock)
+seconds since the whole simulation started.
The state update loop
---------------------
@@ -222,7 +229,7 @@ Sending the final result
.. code-block:: python
# O_F
- instance.send('final_state', Message(t_cur, None, U.tolist()))
+ instance.send('final_state', Message(t_cur, None, Grid(U, ['x'])))
After the update loop is done, the model has arrived at its final state. We
@@ -255,8 +262,11 @@ document, for each port, which data type you're expecting or sending! Your
future colleagues (and possibly your future self) will thank you.
MessagePack is an extensible format, and since sending grids is very common in
-these kinds of models it would be nice if NumPy arrays could be sent directly.
-That's not yet implemented, but should be in a future version of MUSCLE 3.
+these kinds of models MUSCLE 3 supports sending NumPy arrays directly. Here, we
+wrap our array U into a :class:`libmuscle.Grid` object, so that we can add the
+name of the dimensions. In this case there's only one, and ``x`` is not very
+descriptive, so we could have also passed ``U`` directly, in which case MUSCLE
+would have sent a :class:`libmuscle.Grid` without index names automatically.
Finally, if you want to use your own encoding, you can just send a ``bytes``
object, which will be transmitted as-is, with minimal overhead.
@@ -281,20 +291,20 @@ O_I operator.
t_next = t_cur + dt
if t_next + dt > t_max:
t_next = None
- cur_state_msg = Message(t_cur, t_next, U.tolist())
+ cur_state_msg = Message(t_cur, t_next, Grid(U, ['x']))
instance.send('state_out', cur_state_msg)
Since the diffusion model is the macro-submodel in this model, it needs to send
its state to the outside world on every timestep. This is done in the O_I
-operator. The message simply contains the state, converted to a standard Python
-list, and it is sent on the ``state_out`` port, which was declared for the O_I
-operator when we made the :class:`libmuscle.Instance` for this model. The
-message is sent with the current simulation time, and a second timestamp that
-gives the simulation time for the next message that will be sent on this port.
-Since our time steps are fixed, this is easy to calculate. We do need to take
-care to send ``None`` if this is the final message on this port however, since
-there won't be another message in that case.
+operator. The message simply contains the state, converted to a
+:class:`libmuscle.Grid`, and it is sent on the ``state_out`` port, which was
+declared for the O_I operator when we made the :class:`libmuscle.Instance` for
+this model. The message is sent with the current simulation time, and a second
+timestamp that gives the simulation time for the next message that will be sent
+on this port. Since our time steps are fixed, this is easy to calculate. We do
+need to take care to send ``None`` if this is the final message on this port
+however, since there won't be another message in that case.
This deserves a bit more explanation. First, MUSCLE 3 does not use the
timestamps that are attached to the messages for anything, and in this
@@ -326,7 +336,7 @@ Receiving messages with a default
msg = instance.receive('state_in', default=cur_state_msg)
if msg.timestamp > t_cur + dt:
logger.warning('Received a message from the future!')
- U = np.array(msg.data)
+ np.copyto(U, msg.data.array)
The diffusion model being the macro-model, it will need to receive input for its
@@ -335,7 +345,7 @@ state update from the micro-model, which it does by calling
are passing a default message. The default message is returned if this port is
not connected. We are cleverly passing the message containing our current
state, so that if this port is not connected, the model continues from its
-current state. Since MUSCLE 3 will simply ignore a send command on a
+current state. Since MUSCLE 3 will simply ignore a send command on a
disconnected port, this makes it possible to run the diffusion model without a
micro-model attached.
@@ -347,17 +357,26 @@ if the micro-model runs for longer than our macro-model timestep. In this case,
the number of steps is fixed, so that this warning will never be emitted.
However, if the micro-model runs until it detects convergence, then it can
happen that it runs for longer than the timestep of the macro-model, and that
-would indicate that there is no timescale overlap anymore. In that case, the
+would indicate that there is no timescale separation anymore. In that case, the
result could be wrong, and a warning is appropriate.
-The S operator here calls the ``laplacian()`` function. There is no requirement
-for a submodel to be a single function, you can split it up, call library
-functions, and so on. There has to be a top-level function however if you want
-to run more than one submodel in a single Python program. Also, you cannot share
-any data with other components, other than by sending and receiving messages. In
-particular, you can't use global variables to communicate between models. This
-is intentional, because it doesn't work if you're running as separate programs
-on different computers.
+Then, we copy the received data into our state array. The received
+:class:`libmuscle.Grid` object contains a read-only array, and just writing ``U
+= msg.data.array`` would discard the state we have in ``U``, and instead make
+the variable ``U`` refer to the received read-only array. We would then get an
+error message when trying to update the state. The ``np.copyto`` function
+instead copies the (read-only) contents of ``msg.data.array`` into the existing
+(writable) array referred to by ``U``. This way, we can do our state update, and
+it's also a bit more efficient than reallocating memory all the time.
+
+The S operator here calls the ``laplacian()`` function (not shown). There is no
+requirement for a submodel to be a single function, you can split it up, call
+library functions, and so on. There has to be a top-level function however if
+you want to run more than one submodel in a single Python program. Also, you
+cannot share any data with other components, other than by sending and receiving
+messages. In particular, you can't use global variables to communicate between
+models. This is intentional, because it doesn't work if you're running as
+separate programs on different computers.
Connecting it all together
diff --git a/integration_test/conftest.py b/integration_test/conftest.py
index f829a490..46bac069 100644
--- a/integration_test/conftest.py
+++ b/integration_test/conftest.py
@@ -1,5 +1,6 @@
import logging
import multiprocessing as mp
+import os
import sys
from typing import Generator
@@ -16,6 +17,11 @@
from libmuscle.manager.topology_store import TopologyStore
+skip_if_python_only = pytest.mark.skipif(
+ 'MUSCLE_TEST_PYTHON_ONLY' in os.environ,
+ reason='Python-only tests requested')
+
+
@pytest.fixture
def yatiml_log_warning():
yatiml.logger.setLevel(logging.WARNING)
diff --git a/integration_test/test_all.py b/integration_test/test_all.py
index ee3e9d2b..780c0b32 100644
--- a/integration_test/test_all.py
+++ b/integration_test/test_all.py
@@ -2,11 +2,12 @@
import sys
from typing import List
+import numpy as np
import pytest
from ymmsl import (ComputeElement, Conduit, Configuration, Model, Operator,
Reference, Settings)
-from libmuscle import Instance, Message
+from libmuscle import Grid, Instance, Message
from libmuscle.runner import run_simulation
@@ -29,7 +30,11 @@ def macro():
# s/b
for slot in range(10):
msg = instance.receive('in', slot)
- assert msg.data == 'testing back'
+ assert msg.data['string'] == 'testing back'
+ assert msg.data['int'] == 42
+ assert msg.data['float'] == 3.1416
+ assert msg.data['grid'].array.dtype == np.float64
+ assert msg.data['grid'].array[0, 1] == 34.0
def micro():
@@ -49,7 +54,12 @@ def micro():
assert msg.data == 'testing'
# o_f
- instance.send('out', Message(0.1, None, 'testing back'))
+ result = {
+ 'string': 'testing back',
+ 'int': 42,
+ 'float': 3.1416,
+ 'grid': Grid(np.array([[12.0, 34.0, 56.0], [1.0, 2.0, 3.0]]))}
+ instance.send('out', Message(0.1, None, result))
def test_all(log_file_in_tmpdir):
diff --git a/integration_test/test_cpp_macro_micro.py b/integration_test/test_cpp_macro_micro.py
index 80b9b7d0..b06e6e48 100644
--- a/integration_test/test_cpp_macro_micro.py
+++ b/integration_test/test_cpp_macro_micro.py
@@ -3,9 +3,13 @@
import subprocess
import sys
+import numpy as np
+
from libmuscle import Instance, Message
from ymmsl import Operator
+from .conftest import skip_if_python_only
+
def run_macro(instance_id: str):
sys.argv.append('--muscle-instance={}'.format(instance_id))
@@ -23,14 +27,24 @@ def macro():
for i in range(2):
# o_i
- instance.send('out', Message(i * 10.0, (i + 1) * 10.0, 'testing'))
+ test_array = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
+ assert test_array.shape == (2, 3)
+ assert test_array.flags.c_contiguous
+ data = {
+ 'message': 'testing',
+ 'test_grid': test_array}
+ instance.send('out', Message(i * 10.0, (i + 1) * 10.0, data))
# s/b
msg = instance.receive('in')
- assert msg.data == 'testing back {}'.format(i)
+ assert msg.data['reply'] == 'testing back {}'.format(i)
+ assert msg.data['test_grid'].array.dtype.kind == 'i'
+ assert msg.data['test_grid'].array.dtype.itemsize == 8
+ assert msg.data['test_grid'].array[0][1] == 2
assert msg.timestamp == i * 10.0
+@skip_if_python_only
def test_cpp_macro_micro(mmp_server_process_simple):
# create C++ micro model
# see libmuscle/cpp/src/libmuscle/tests/micro_model_test.cpp
diff --git a/integration_test/test_cpp_mmp_client.py b/integration_test/test_cpp_mmp_client.py
index 5b0036d7..29b31b1f 100644
--- a/integration_test/test_cpp_mmp_client.py
+++ b/integration_test/test_cpp_mmp_client.py
@@ -14,6 +14,8 @@
from libmuscle.mmp_client import MMPClient
from libmuscle.operator import Operator
+from .conftest import skip_if_python_only
+
def do_mmp_client_test(caplog):
ymmsl_text = (
@@ -80,7 +82,6 @@ def mock_remove(name: Reference):
result = subprocess.run([str(cpp_test_client)], env=env)
# check that C++-side checks were successful
- print(result.stderr, flush=True)
assert result.returncode == 0
# check submit_log_message
@@ -104,6 +105,7 @@ def mock_remove(name: Reference):
server.stop()
+@skip_if_python_only
def test_mmp_client(log_file_in_tmpdir, caplog):
process = mp.Process(target=do_mmp_client_test, args=(caplog,))
process.start()
diff --git a/integration_test/test_cpp_tcp_client.py b/integration_test/test_cpp_tcp_client.py
index 26ec416b..23c61f19 100644
--- a/integration_test/test_cpp_tcp_client.py
+++ b/integration_test/test_cpp_tcp_client.py
@@ -11,6 +11,8 @@
from ymmsl import Reference, Settings
+from .conftest import skip_if_python_only
+
def tcp_server_process(control_pipe):
control_pipe[0].close()
@@ -38,6 +40,7 @@ def get_message(receiver):
server.close()
+@skip_if_python_only
def test_cpp_tcp_client(log_file_in_tmpdir):
# create server process
server_pipe = mp.Pipe()
diff --git a/integration_test/test_cpp_tcp_server.py b/integration_test/test_cpp_tcp_server.py
index 03a343b7..cd1a4e2f 100644
--- a/integration_test/test_cpp_tcp_server.py
+++ b/integration_test/test_cpp_tcp_server.py
@@ -9,7 +9,10 @@
from ymmsl import Reference, Settings
+from .conftest import skip_if_python_only
+
+@skip_if_python_only
def test_cpp_tcp_server(log_file_in_tmpdir):
# create C++ server
# it serves a message for us to receive
diff --git a/integration_test/test_fortran_macro_micro.py b/integration_test/test_fortran_macro_micro.py
index 1837b986..c8f2caa1 100644
--- a/integration_test/test_fortran_macro_micro.py
+++ b/integration_test/test_fortran_macro_micro.py
@@ -3,9 +3,13 @@
import subprocess
import sys
+import numpy as np
+
from libmuscle import Instance, Message
from ymmsl import Operator
+from .conftest import skip_if_python_only
+
def run_macro(instance_id: str):
sys.argv.append('--muscle-instance={}'.format(instance_id))
@@ -23,17 +27,28 @@ def macro():
for i in range(2):
# o_i
- instance.send('out', Message(i * 10.0, (i + 1) * 10.0, 'testing'))
+ test_array = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
+ assert test_array.shape == (2, 3)
+ assert test_array.flags.c_contiguous
+ data = {
+ 'message': 'testing',
+ 'test_grid': test_array}
+ instance.send('out', Message(i * 10.0, (i + 1) * 10.0, data))
# s/b
msg = instance.receive('in')
- assert msg.data == 'testing back {}'.format(i)
+ assert msg.data['reply'] == 'testing back {}'.format(i)
+ assert msg.data['test_grid'].array.shape == (2, 3)
+ assert msg.data['test_grid'].array.flags.f_contiguous
+ assert (msg.data['test_grid'].array ==
+ np.array([[1, 2, 3], [4, 5, 6]])).all()
assert msg.timestamp == i * 10.0
-def test_cpp_macro_micro(mmp_server_process_simple):
+@skip_if_python_only
+def test_fortran_macro_micro(mmp_server_process_simple):
# create C++ micro model
- # see libmuscle/cpp/src/libmuscle/tests/fortran_micro_model_test.f03
+ # see libmuscle/fortran/src/libmuscle/tests/fortran_micro_model_test.f03
cpp_build_dir = Path(__file__).parents[1] / 'libmuscle' / 'cpp' / 'build'
lib_paths = [
cpp_build_dir / 'grpc' / 'c-ares' / 'c-ares' / 'lib',
diff --git a/integration_test/test_mpi_macro_micro.py b/integration_test/test_mpi_macro_micro.py
index e7ac2b01..a4ab66df 100644
--- a/integration_test/test_mpi_macro_micro.py
+++ b/integration_test/test_mpi_macro_micro.py
@@ -8,6 +8,8 @@
from libmuscle import Instance, Message
from ymmsl import Operator
+from .conftest import skip_if_python_only
+
def run_macro(instance_id: str):
sys.argv.append('--muscle-instance={}'.format(instance_id))
@@ -33,6 +35,7 @@ def macro():
assert msg.timestamp == i * 10.0
+@skip_if_python_only
def test_cpp_macro_micro(mmp_server_process_simple):
# only run this if MPI is enabled
if 'MUSCLE_ENABLE_MPI' not in os.environ:
diff --git a/libmuscle/build/.gitignore b/libmuscle/build/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/libmuscle/build/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/libmuscle/cpp/build/Makefile b/libmuscle/cpp/build/Makefile
index 19524f87..6c03d710 100644
--- a/libmuscle/cpp/build/Makefile
+++ b/libmuscle/cpp/build/Makefile
@@ -1,6 +1,5 @@
include check_tools.make
-
.PHONY: all
all: muscle_manager_protocol ymmsl libmuscle
@@ -10,12 +9,9 @@ test: tests
$(MAKE) -C libmuscle test
.PHONY: install
-install: all
+install: all protobuf_install grpc_install msgpack_install
$(MAKE) -C ymmsl install
$(MAKE) -C libmuscle install
- $(MAKE) -C protobuf install
- $(MAKE) -C grpc install
- $(MAKE) -C msgpack install
.PHONY: clean
clean:
@@ -37,37 +33,48 @@ distclean:
$(MAKE) -C ymmsl distclean
$(MAKE) -C libmuscle distclean
+# Find dependencies or set up build
dep_name := protobuf
-dep_min_version := 3.7.1
-dep_version := 3.7.1
+dep_version_constraint := = 3.10.0
+dep_version := 3.10.0
dep_pkgconfig_name := protobuf
-include make_available.make
-
-export protobuf_ROOT # make available to grpc as well
+dep_install := 1
+include $(TOOLDIR)/make_available.make
dep_name := grpc
-dep_min_version := 1.17.1
-dep_version := 1.17.1
-dep_pkgconfig_name := grpc
-include make_available.make
+dep_version_constraint := = 1.24.3
+dep_version := 1.24.3
+dep_pkgconfig_name := grpc++
+dep_install := 1
+include $(TOOLDIR)/make_available.make
+
+# If gRPC is available but we're building our own protobuf, then we should
+# build our own gRPC as well, linked against our own protobuf, to avoid having
+# two versions of protobuf in the same process.
+ifeq (grpc_AVAILABLE, 1)
+ ifneq (protobuf_AVAILABLE, 1)
+ include $(TOOLDIR)/dep_build.make
+ endif
+endif
grpc: protobuf
dep_name := msgpack
-dep_min_version := 3.1.0
+dep_version_constraint := >= 3.1.0
dep_version := 3.2.0
dep_pkgconfig_name := msgpack
-include make_available.make
+dep_install := 1
+include $(TOOLDIR)/make_available.make
dep_name := googletest
-dep_min_version := 1.8.1
+dep_version_constraint := >= 1.8.1
dep_version := 1.8.1
dep_pkgconfig_name := gtest
-include make_available.make
-
-export googletest_ROOT
+dep_install := 0
+include $(TOOLDIR)/make_available.make
+# Directories where local builds will be available
DEP_DIRS := $(CURDIR)/protobuf/protobuf
DEP_DIRS += $(CURDIR)/grpc/c-ares/c-ares
diff --git a/libmuscle/cpp/build/check_tools.make b/libmuscle/cpp/build/check_tools.make
index b3142c04..892f4dde 100644
--- a/libmuscle/cpp/build/check_tools.make
+++ b/libmuscle/cpp/build/check_tools.make
@@ -1,69 +1,87 @@
# Make module that verifies that we have all needed tools
-$(info Checking for tools...)
+$(info )
-# Check C++ compiler
-ifndef CXX
- _gcc_version := $(shell g++ --version || echo NOTFOUND)
- ifneq ($(_gcc_version), NOTFOUND)
- export CXX = g++
- $(info Found g++ version $(shell g++ --version | head -n 1))
- endif
+# Check Python version
+$(info Looking for Python...)
+_python_version := $(shell python3 --version || echo NOTFOUND)
+ifneq ($(_python_version), NOTFOUND)
+ $(info - Found Python version $(_python_version))
+else
+ $(info - Python 3 not found)
endif
-ifndef CXX
- _clang_version := $(shell clang++ --version || echo NOTFOUND)
- ifneq ($(_clang_version), NOTFOUND)
- export CXX = clang++
- $(info Found clang version $(shell clang++ --version | head -n 1))
- endif
-endif
+# Check C++ compiler
+$(info )
+$(info Looking for C++ compiler...)
+tool_var := CXX
+include $(TOOLDIR)/check_override.make
+
+tool_command := g++
+include $(TOOLDIR)/detect_tool_implicit.make
+tool_command := clang++
+include $(TOOLDIR)/detect_tool_implicit.make
ifndef CXX
- $(error No C++ compiler found! Please install either gcc or clang.)
+ $(error - No C++ compiler found! Please install either gcc or clang.)
else
- $(info - Will compile C++ files using $(CXX); set CXX to override.)
+ $(info - Will compile C++ files using $(CXX).)
endif
+# Check MPI C++ compiler, if MPI is enabled
+ifdef MUSCLE_ENABLE_MPI
+ $(info )
+ $(info Looking for MPI C++ compiler...)
+ tool_var := MPICXX
+ include $(TOOLDIR)/check_override.make
-# Check download tool (for downloading dependencies)
-ifndef DOWNLOAD
- _wget_version := $(shell wget --version || echo NOTFOUND)
- ifneq ($(_wget_version), NOTFOUND)
- export DOWNLOAD = wget
- $(info Found wget version $(shell wget --version | head -n 1).)
+ tool_command := mpi$(CXX)
+ include $(TOOLDIR)/detect_tool.make
+ tool_command := mpic++
+ include $(TOOLDIR)/detect_tool.make
+
+ ifndef MPICXX
+ $(error - No MPI C++ compiler found! Maybe there's no MPI installed?)
+ else
+ $(info - Will compile MPI C++ files using $(MPICXX).)
endif
endif
-ifndef DOWNLOAD
- _curl_version := $(shell curl --version || echo NOTFOUND)
- ifneq ($(_curl_version), NOTFOUND)
- export DOWNLOAD = curl
- $(info Found curl version $(shell curl --version | head -n 1).)
- endif
+# Check download tool (for downloading dependencies)
+$(info )
+$(info Looking for download tool...)
+tool_var := DOWNLOAD
+include $(TOOLDIR)/check_override.make
+
+tool_command := wget
+include $(TOOLDIR)/detect_tool.make
+tool_command := curl
+include $(TOOLDIR)/detect_tool.make
+
+ifeq ($(DOWNLOAD), curl)
+ export DOWNLOAD := curl -LO
endif
ifndef DOWNLOAD
- $(warning Could not find either wget or curl, so I won't be able to download dependencies.)
- $(warning To fix this, set DOWNLOAD to a command that can download http links.)
+ $(warning - Could not find either wget or curl, so I won't be able to download dependencies.)
+ $(warning - To fix this, install wget or curl, or set DOWNLOAD to a command that can download http links.)
else
- $(info - Will download files using $(DOWNLOAD); set DOWNLOAD to override.)
+ $(info - Will download files using $(DOWNLOAD).)
endif
-
# Check tar tool (for unpacking dependencies)
-ifndef TAR
- _tar_version := $(shell tar --version || echo NOTFOUND)
- ifneq ($(_tar_version), NOTFOUND)
- export TAR = tar
- $(info Found tar version $(shell tar --version | head -n 1).)
- endif
-endif
+$(info )
+$(info Looking for tar...)
+tool_var := TAR
+include $(TOOLDIR)/check_override.make
+
+tool_command := tar
+include $(TOOLDIR)/detect_tool.make
ifndef TAR
- $(warning Could not find tar, so I won't be able to download dependencies.)
- $(warning To fix this, set TAR to a command that can extract tar archives.)
+ $(warning - Could not find tar, so I won't be able to unpack dependencies.)
+ $(warning - To fix this, set TAR to a command that can extract tar archives.)
else
- $(info - Will extract archives using $(TAR); set TAR to override.)
+ $(info - Will extract archives using $(TAR).)
endif
# Check number of cores
@@ -71,5 +89,6 @@ ifndef NCORES
NCORES := $(shell nproc 2>/dev/null || echo 2)
export NCORES
endif
+$(info )
$(info Using $(NCORES) cores to build; set NCORES to override.)
-
+$(info )
diff --git a/libmuscle/cpp/build/grpc/.gitignore b/libmuscle/cpp/build/grpc/.gitignore
index a42e8976..72628665 100644
--- a/libmuscle/cpp/build/grpc/.gitignore
+++ b/libmuscle/cpp/build/grpc/.gitignore
@@ -1,6 +1,7 @@
*
!.gitignore
!Makefile
+!grpc_issue_14844.patch
!/c-ares
!/zlib
!/protobuf
diff --git a/libmuscle/cpp/build/grpc/Makefile b/libmuscle/cpp/build/grpc/Makefile
index 36cfb9fc..5c688488 100644
--- a/libmuscle/cpp/build/grpc/Makefile
+++ b/libmuscle/cpp/build/grpc/Makefile
@@ -3,16 +3,10 @@ all: grpc
@echo
.PHONY: install
-ifdef grpc_VERSION
-install: grpc
- $(MAKE) -C c-ares install
- $(MAKE) -C zlib install
- $(MAKE) -C openssl install
- mkdir -p $(PREFIX)/lib
- cp -ra grpc/lib/* $(PREFIX)/lib/
-else
-install:
-endif
+install: all c-ares_install zlib_install openssl_install
+ @echo 'Installing grpc...'
+ mkdir -p $(PREFIX)
+ cp -ra grpc/* $(PREFIX)/
.PHONY: clean
clean:
@@ -31,28 +25,32 @@ distclean: clean
dep_name := c-ares
-dep_min_version := 1.11.0
+dep_version_constraint := >= 1.11.0
dep_version := 1.15.0
dep_pkgconfig_name := libcares
-include ../make_available.make
+dep_install := 1
+include $(TOOLDIR)/make_available.make
dep_name := zlib
-dep_min_version := 1.2
+dep_version_constraint := >= 1.2
dep_version := 1.2.11
dep_pkgconfig_name := zlib
-include ../make_available.make
+dep_install := 1
+include $(TOOLDIR)/make_available.make
dep_name := openssl
-dep_min_version := 1.0.2
-dep_version := 1.1.1c
+dep_version_constraint := >= 1.0.2
+dep_version := 1.1.1f
dep_pkgconfig_name := openssl
-include ../make_available.make
+dep_install := 1
+include $(TOOLDIR)/make_available.make
v$(grpc_VERSION).tar.gz:
$(DOWNLOAD) https://github.com/grpc/grpc/archive/v$(grpc_VERSION).tar.gz
grpc-$(grpc_VERSION): v$(grpc_VERSION).tar.gz
$(TAR) xf v$(grpc_VERSION).tar.gz
+ patch -p0 $@
+ @echo 'exec_prefix=$${prefix}' >>$@
+ @echo 'includedir=$${prefix}/include' >>$@
+ @echo 'libdir=$${exec_prefix}/lib' >>$@
+ @echo >>$@
+ @echo 'Name: libmuscle for C++' >>$@
+ @echo 'Description: Library for MUSCLE 3' >>$@
+ @echo 'URL: https://muscle3.readthedocs.io' >>$@
+ @echo 'Version: $(muscle_version)' >>$@
+ @echo 'Requires.private: grpc++ >= 1.24.3, protobuf >= 3.10.0, msgpack >= 3.1.0' >>$@
+ @echo 'Cflags: -I$${includedir}' >>$@
+ @echo 'Libs: -L$${libdir} -lymmsl -lmuscle' >>$@
+
+libmuscle_mpi.pc:
+ @echo 'prefix=$(PREFIX)' >$@
+ @echo 'exec_prefix=$${prefix}' >>$@
+ @echo 'includedir=$${prefix}/include' >>$@
+ @echo 'libdir=$${exec_prefix}/lib' >>$@
+ @echo >>$@
+ @echo 'Name: libmuscle for C++ and MPI' >>$@
+ @echo 'Description: Library for MUSCLE 3' >>$@
+ @echo 'URL: https://muscle3.readthedocs.io' >>$@
+ @echo 'Version: $(muscle_version)' >>$@
+ @echo 'Requires.private: grpc++ >= 1.24.3, protobuf >= 3.10.0, msgpack >= 3.1.0' >>$@
+ @echo 'Cflags: -I$${includedir} -DMUSCLE_ENABLE_MPI' >>$@
+ @echo 'Libs: -L$${libdir} -lymmsl -lmuscle_mpi' >>$@
+
diff --git a/libmuscle/cpp/build/libmuscle/libmuscle.version b/libmuscle/cpp/build/libmuscle/libmuscle.version
index e5f8079c..fef68798 100644
--- a/libmuscle/cpp/build/libmuscle/libmuscle.version
+++ b/libmuscle/cpp/build/libmuscle/libmuscle.version
@@ -51,6 +51,16 @@
"libmuscle::impl::DataConstRef::DataConstRef(double)";
"libmuscle::impl::DataConstRef::DataConstRef(ymmsl::impl::SettingValue const&)";
"libmuscle::impl::DataConstRef::DataConstRef(ymmsl::impl::Settings const&)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(bool const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(bool const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(double const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(double const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(float const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(float const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(int const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(int const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(long const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(long const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
"libmuscle::impl::DataConstRef::reseat(libmuscle::impl::DataConstRef const&)";
"libmuscle::impl::DataConstRef::size() const";
"bool libmuscle::impl::DataConstRef::is_a() const";
@@ -72,6 +82,11 @@
"bool libmuscle::impl::DataConstRef::is_a() const";
"libmuscle::impl::DataConstRef::is_a_dict() const";
"libmuscle::impl::DataConstRef::is_a_list() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
"libmuscle::impl::DataConstRef::is_a_byte_array() const";
"libmuscle::impl::DataConstRef::is_nil() const";
"libmuscle::impl::DataConstRef::as_byte_array() const";
@@ -82,7 +97,26 @@
"libmuscle::impl::DataConstRef::operator[](std::string const&) const";
"libmuscle::impl::DataConstRef::key(unsigned long) const";
"libmuscle::impl::DataConstRef::value(unsigned long) const";
+ "bool const* libmuscle::impl::DataConstRef::elements() const";
+ "double const* libmuscle::impl::DataConstRef::elements() const";
+ "float const* libmuscle::impl::DataConstRef::elements() const";
+ "int const* libmuscle::impl::DataConstRef::elements() const";
+ "long const* libmuscle::impl::DataConstRef::elements() const";
+ "libmuscle::impl::DataConstRef::has_indexes() const";
+ "libmuscle::impl::DataConstRef::indexes[abi:cxx11]() const";
+ "libmuscle::impl::DataConstRef::shape() const";
+ "libmuscle::impl::DataConstRef::storage_order() const";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(bool const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(bool const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(double const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(double const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(float const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(float const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(int const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(int const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(long const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(long const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
"libmuscle::impl::Data::byte_array(char const*, unsigned int)";
"libmuscle::impl::Data::byte_array(unsigned int)";
"libmuscle::impl::Data::dict()";
@@ -106,6 +140,16 @@
LIBMUSCLE_DataConstRef_create_real8_;
LIBMUSCLE_DataConstRef_create_settings_;
LIBMUSCLE_DataConstRef_create_copy_;
+ LIBMUSCLE_DataConstRef_create_grid_logical_a_;
+ LIBMUSCLE_DataConstRef_create_grid_int4_a_;
+ LIBMUSCLE_DataConstRef_create_grid_int8_a_;
+ LIBMUSCLE_DataConstRef_create_grid_real4_a_;
+ LIBMUSCLE_DataConstRef_create_grid_real8_a_;
+ LIBMUSCLE_DataConstRef_create_grid_logical_n_;
+ LIBMUSCLE_DataConstRef_create_grid_int4_n_;
+ LIBMUSCLE_DataConstRef_create_grid_int8_n_;
+ LIBMUSCLE_DataConstRef_create_grid_real4_n_;
+ LIBMUSCLE_DataConstRef_create_grid_real8_n_;
LIBMUSCLE_DataConstRef_free_;
LIBMUSCLE_DataConstRef_is_a_logical_;
LIBMUSCLE_DataConstRef_is_a_character_;
@@ -118,6 +162,11 @@
LIBMUSCLE_DataConstRef_is_a_real8_;
LIBMUSCLE_DataConstRef_is_a_dict_;
LIBMUSCLE_DataConstRef_is_a_list_;
+ LIBMUSCLE_DataConstRef_is_a_grid_of_logical_;
+ LIBMUSCLE_DataConstRef_is_a_grid_of_real4_;
+ LIBMUSCLE_DataConstRef_is_a_grid_of_real8_;
+ LIBMUSCLE_DataConstRef_is_a_grid_of_int4_;
+ LIBMUSCLE_DataConstRef_is_a_grid_of_int8_;
LIBMUSCLE_DataConstRef_is_a_byte_array_;
LIBMUSCLE_DataConstRef_is_nil_;
LIBMUSCLE_DataConstRef_is_a_settings_;
@@ -135,6 +184,15 @@
LIBMUSCLE_DataConstRef_as_byte_array_;
LIBMUSCLE_DataConstRef_get_item_by_key_;
LIBMUSCLE_DataConstRef_get_item_by_index_;
+ LIBMUSCLE_DataConstRef_num_dims_;
+ LIBMUSCLE_DataConstRef_shape_;
+ LIBMUSCLE_DataConstRef_elements_logical_;
+ LIBMUSCLE_DataConstRef_elements_int4_;
+ LIBMUSCLE_DataConstRef_elements_int8_;
+ LIBMUSCLE_DataConstRef_elements_real4_;
+ LIBMUSCLE_DataConstRef_elements_real8_;
+ LIBMUSCLE_DataConstRef_has_indexes_;
+ LIBMUSCLE_DataConstRef_index_;
LIBMUSCLE_Data_create_nil_;
LIBMUSCLE_Data_create_logical_;
LIBMUSCLE_Data_create_character_;
@@ -146,6 +204,16 @@
LIBMUSCLE_Data_create_real8_;
LIBMUSCLE_Data_create_settings_;
LIBMUSCLE_Data_create_copy_;
+ LIBMUSCLE_Data_create_grid_logical_a_;
+ LIBMUSCLE_Data_create_grid_int4_a_;
+ LIBMUSCLE_Data_create_grid_int8_a_;
+ LIBMUSCLE_Data_create_grid_real4_a_;
+ LIBMUSCLE_Data_create_grid_real8_a_;
+ LIBMUSCLE_Data_create_grid_logical_n_;
+ LIBMUSCLE_Data_create_grid_int4_n_;
+ LIBMUSCLE_Data_create_grid_int8_n_;
+ LIBMUSCLE_Data_create_grid_real4_n_;
+ LIBMUSCLE_Data_create_grid_real8_n_;
LIBMUSCLE_Data_free_;
LIBMUSCLE_Data_is_a_logical_;
LIBMUSCLE_Data_is_a_character_;
@@ -158,6 +226,11 @@
LIBMUSCLE_Data_is_a_real8_;
LIBMUSCLE_Data_is_a_dict_;
LIBMUSCLE_Data_is_a_list_;
+ LIBMUSCLE_Data_is_a_grid_of_logical_;
+ LIBMUSCLE_Data_is_a_grid_of_real4_;
+ LIBMUSCLE_Data_is_a_grid_of_real8_;
+ LIBMUSCLE_Data_is_a_grid_of_int4_;
+ LIBMUSCLE_Data_is_a_grid_of_int8_;
LIBMUSCLE_Data_is_a_byte_array_;
LIBMUSCLE_Data_is_nil_;
LIBMUSCLE_Data_is_a_settings_;
@@ -175,6 +248,15 @@
LIBMUSCLE_Data_as_byte_array_;
LIBMUSCLE_Data_get_item_by_key_;
LIBMUSCLE_Data_get_item_by_index_;
+ LIBMUSCLE_Data_num_dims_;
+ LIBMUSCLE_Data_shape_;
+ LIBMUSCLE_Data_elements_logical_;
+ LIBMUSCLE_Data_elements_int4_;
+ LIBMUSCLE_Data_elements_int8_;
+ LIBMUSCLE_Data_elements_real4_;
+ LIBMUSCLE_Data_elements_real8_;
+ LIBMUSCLE_Data_has_indexes_;
+ LIBMUSCLE_Data_index_;
LIBMUSCLE_Data_create_dict_;
LIBMUSCLE_Data_create_list_;
LIBMUSCLE_Data_create_nils_;
diff --git a/libmuscle/cpp/build/libmuscle/libmuscle.version.in b/libmuscle/cpp/build/libmuscle/libmuscle.version.in
index 09f9635f..5163da10 100644
--- a/libmuscle/cpp/build/libmuscle/libmuscle.version.in
+++ b/libmuscle/cpp/build/libmuscle/libmuscle.version.in
@@ -51,6 +51,16 @@
"libmuscle::impl::DataConstRef::DataConstRef(double)";
"libmuscle::impl::DataConstRef::DataConstRef(ymmsl::impl::SettingValue const&)";
"libmuscle::impl::DataConstRef::DataConstRef(ymmsl::impl::Settings const&)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(bool const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(bool const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(double const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(double const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(float const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(float const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(int const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(int const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(long const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::DataConstRef libmuscle::impl::DataConstRef::grid(long const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
"libmuscle::impl::DataConstRef::reseat(libmuscle::impl::DataConstRef const&)";
"libmuscle::impl::DataConstRef::size() const";
"bool libmuscle::impl::DataConstRef::is_a() const";
@@ -72,6 +82,11 @@
"bool libmuscle::impl::DataConstRef::is_a() const";
"libmuscle::impl::DataConstRef::is_a_dict() const";
"libmuscle::impl::DataConstRef::is_a_list() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
+ "bool libmuscle::impl::DataConstRef::is_a_grid_of() const";
"libmuscle::impl::DataConstRef::is_a_byte_array() const";
"libmuscle::impl::DataConstRef::is_nil() const";
"libmuscle::impl::DataConstRef::as_byte_array() const";
@@ -82,7 +97,26 @@
"libmuscle::impl::DataConstRef::operator[](std::string const&) const";
"libmuscle::impl::DataConstRef::key(unsigned long) const";
"libmuscle::impl::DataConstRef::value(unsigned long) const";
+ "bool const* libmuscle::impl::DataConstRef::elements() const";
+ "double const* libmuscle::impl::DataConstRef::elements() const";
+ "float const* libmuscle::impl::DataConstRef::elements() const";
+ "int const* libmuscle::impl::DataConstRef::elements() const";
+ "long const* libmuscle::impl::DataConstRef::elements() const";
+ "libmuscle::impl::DataConstRef::has_indexes() const";
+ "libmuscle::impl::DataConstRef::indexes[abi:cxx11]() const";
+ "libmuscle::impl::DataConstRef::shape() const";
+ "libmuscle::impl::DataConstRef::storage_order() const";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(bool const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(bool const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(double const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(double const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(float const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(float const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(int const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(int const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(long const*, std::vector > const&, std::vector, std::allocator >, std::allocator, std::allocator > > > const&, libmuscle::impl::StorageOrder)";
+ "libmuscle::impl::Data libmuscle::impl::Data::grid(long const*, std::vector > const&, std::vector > const&, libmuscle::impl::StorageOrder)";
"libmuscle::impl::Data::byte_array(char const*, unsigned int)";
"libmuscle::impl::Data::byte_array(unsigned int)";
"libmuscle::impl::Data::dict()";
diff --git a/libmuscle/cpp/build/libmuscle/libmuscle_mpi.version b/libmuscle/cpp/build/libmuscle/libmuscle_mpi.version
new file mode 100644
index 00000000..fa6c204a
--- /dev/null
+++ b/libmuscle/cpp/build/libmuscle/libmuscle_mpi.version
@@ -0,0 +1,358 @@
+{
+ global:
+ extern "C++" {
+ libmuscle::impl::Instance::Instance*;
+ "libmuscle::impl::Instance::~Instance()";
+ libmuscle::impl::Instance::reuse_instance*;
+ libmuscle::impl::Instance::error_shutdown*;
+ libmuscle::impl::Instance::set_port_length*;
+ libmuscle::impl::Instance::get_port_length*;
+ libmuscle::impl::Instance::is_connected*;
+ libmuscle::impl::Instance::is_resizable*;
+ libmuscle::impl::Instance::is_vector_port*;
+ libmuscle::impl::Instance::list_ports*;
+ libmuscle::impl::Instance::get_setting*;
+ "bool libmuscle::impl::Instance::get_setting_as(std::__cxx11::basic_string, std::allocator > const&) const";
+ "bool libmuscle::impl::Instance::get_setting_as(std::string const&) const";
+ "int64_t libmuscle::impl::Instance::get_setting_as(std::__cxx11::basic_string, std::allocator > const&) const";
+ "int64_t libmuscle::impl::Instance::get_setting_as(std::string const&) const";
+ "int libmuscle::impl::Instance::get_setting_as(std::__cxx11::basic_string, std::allocator > const&) const";
+ "int libmuscle::impl::Instance::get_setting_as(std::string const&) const";
+ "long libmuscle::impl::Instance::get_setting_as(std::__cxx11::basic_string, std::allocator