Repository: myokit/myokit (language: python)

Issue:
Various errors and warnings when compiling OpenCL kernels
On @DavAug 's machine at least :-D

```
OpenCL Error: Fiber kernel failed to compile.
-------------------------------------------------------------------------------
<program source>:66:6: warning: no previous prototype for function 'calc_membrane'
void calc_membrane(const uint of1, const uint of2, const uint of3, __global Real *state, __global Real *inter_log, const __global Real *field_data, Real V_IK, Real V_IK1, Real V_IKp, Real V_INa, Real V_Ib, Real pace, Real idiff, __private Real *D_V)
     ^
<program source>:76:6: warning: no previous prototype for function 'calc_ina'
void calc_ina(const uint of1, const uint of2, const uint of3, __global Real *state, __global Real *inter_log, const __global Real *field_data, __private Real *V_INa, __private Real *D_h, __private Real *D_j, __private Real *D_m)
     ^
```


Discussion / hints:
```
<program source>:78:35: error: call to '__fast_relax_exp' is ambiguous
    Real V_a = 1.0 - 1.0 / (1.0 + exp((-(V_V + 40.0)) / 0.24));
                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4544:22: note: expanded from macro 'exp'
    #define exp(__x) __fast_relax_exp(__x)
                     ^~~~~~~~~~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
    __CLFN_FD_1FD_FAST_RELAX(__fast_relax_exp, native_exp, __cl_exp);
                             ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:417:27: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float __OVERLOAD__ _name(float x) { return _default_name(x); } \
                          ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:418:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float2 __OVERLOAD__ _name(float2 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:419:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float3 __OVERLOAD__ _name(float3 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:420:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float4 __OVERLOAD__ _name(float4 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:421:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float8 __OVERLOAD__ _name(float8 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4542:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:422:29: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float16 __OVERLOAD__ _name(float16 x){ return _default_name(x); }
                            ^
```

```
<program source>:82:22: error: call to '__cl_pow' is ambiguous
    *V_INa = V_gNa * pow(V_m, 3.0) * V_h * V_j * (V_V - V_ENa);
                     ^~~~~~~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4709:27: note: expanded from macro 'pow'
    #define pow(__x, __y) __cl_pow(__x, __y)
                          ^~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
__CLFN_FD_2FD(__cl_pow);
              ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1010:48: note: expanded from macro '__CLFN_FD_2FD'
#define __CLFN_FD_2FD(name) float __OVERLOAD__ name(float x, float y); \
                                               ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1011:21: note: expanded from macro '__CLFN_FD_2FD'
float2 __OVERLOAD__ name(float2 x, float2 y); \
                    ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1012:21: note: expanded from macro '__CLFN_FD_2FD'
float3 __OVERLOAD__ name(float3 x, float3 y); \
                    ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1013:21: note: expanded from macro '__CLFN_FD_2FD'
float4 __OVERLOAD__ name(float4 x, float4 y); \
                    ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1014:21: note: expanded from macro '__CLFN_FD_2FD'
float8 __OVERLOAD__ name(float8 x, float8 y); \
                    ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4706:15: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:1015:22: note: expanded from macro '__CLFN_FD_2FD'
float16 __OVERLOAD__ name(float16 x, float16 y);
                     ^
```

These all look like they could be related to literals, but then there's also:

```
<program source>:100:40: error: call to '__fast_relax_log' is ambiguous
    *V_IK = V_gK * V_xi * V_x * (V_V - V_ik_E);
                                       ^~~~~~
<program source>:40:25: note: expanded from macro 'V_ik_E'
#define V_ik_E (V_RTF * log((V_K_o + V_PNa_K * V_Na_o) / (V_K_i + V_PNa_K * V_Na_i)))
                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4655:22: note: expanded from macro 'log'
    #define log(__x) __fast_relax_log(__x)
                     ^~~~~~~~~~~~~~~~
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
    __CLFN_FD_1FD_FAST_RELAX(__fast_relax_log, native_log, __cl_log);
                             ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:417:27: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float __OVERLOAD__ _name(float x) { return _default_name(x); } \
                          ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:418:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float2 __OVERLOAD__ _name(float2 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:419:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float3 __OVERLOAD__ _name(float3 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:420:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float4 __OVERLOAD__ _name(float4 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:421:28: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float8 __OVERLOAD__ _name(float8 x) { return _default_name(x); } \
                           ^
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:4653:30: note: candidate function
/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/A/lib/clang/3.5/include/cl_kernel.h:422:29: note: expanded from macro '__CLFN_FD_1FD_FAST_RELAX'
inline float16 __OVERLOAD__ _name(float16 x){ return _default_name(x); }
                            ^
<program source>:106:6: warning: no previous prototype for function 'calc_ikp'
void calc_ikp(const uint of1, const uint of2, const uint of3, __global Real *state, __global Real *inter_log, const __global Real *field_data, __private Real *V_IKp)
     ^
```

Update: Could still be literals! There's macros in there.
Only three tests fail:

```
======================================================================
ERROR: test_against_cvode (__main__.FiberTissueSimulationTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "myokit/tests/test_simulation_opencl.py", line 984, in test_against_cvode
    d1, logt = s1.run(tmax, logf=logvars, logt=logt, log_interval=dlog)
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/fiber_tissue.py", line 710, in run
    duration, logf, logt, log_interval, report_nan, progress, msg)
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/fiber_tissue.py", line 874, in _run
    [x.qname().encode('ascii') for x in inter_logt],
Exception: OpenCL error: CL_BUILD_PROGRAM_FAILURE

======================================================================
ERROR: test_against_cvode (__main__.SimulationOpenCL0dTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "myokit/tests/test_simulation_opencl.py", line 56, in test_against_cvode
    d1 = s1.run(tmax, logvars, log_interval=dt).npview()
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/openclsim.py", line 906, in run
    r = self._run(duration, log, log_interval, report_nan, progress, msg)
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/openclsim.py", line 1022, in _run
    field_data,
Exception: OpenCL error: CL_BUILD_PROGRAM_FAILURE

======================================================================
ERROR: test_connections_simple_double_precision (__main__.SimulationOpenCLTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "myokit/tests/test_simulation_opencl.py", line 564, in test_connections_simple_double_precision
    d1a = s1.run(t, log=log, log_interval=0.1).npview()
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/openclsim.py", line 906, in run
    r = self._run(duration, log, log_interval, report_nan, progress, msg)
  File "/Users/david/Workspace/sabs-r3/myokit/myokit/_sim/openclsim.py", line 1022, in _run
    field_data,
Exception: OpenCL error: CL_BUILD_PROGRAM_FAILURE
```
All three failing tests use double precision, so literals are correct
Can't find much on why this would happen. Except that the device maybe doesn't support doubles (but fails to raise an error when the extension is enabled)

https://www.ghcc.net/node/1012883
None of the "ambiguous overloads" are actually appropriate, since the operations are with doubles, not floats. The ambiguity comes from trying to cast to float
From @DavAug 👍🏻 

```
Platform 0
 Name       : Apple
 Vendor     : Apple
 Version    : OpenCL 1.2 (Jun  8 2020 17:36:15)
 Profile    : FULL_PROFILE
 Extensions : cl_APPLE_SetMemObjectDestructor cl_APPLE_ContextLoggingFunctions cl_APPLE_clut cl_APPLE_query_kernel_names cl_APPLE_gl_sharing cl_khr_gl_event
```

So extension is not present! Will try to add a check before running then...

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46fd5fcdb..a13eaeee8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,8 +17,9 @@ This page lists the main changes made to Myokit in each release.
   - [#735](https://github.com/MichaelClerx/myokit/pull/735) The new module `myokit.float` contains several functions related to floating point numbers that were previously in the main `myokit` namespace or not part of the public API.
   - [#735](https://github.com/MichaelClerx/myokit/pull/735) The new module `myokit.tools` contains several stand-alone tools that are used by Myokit and were previously in the main `myokit` namespace or not part of the public API.
   - [#735](https://github.com/MichaelClerx/myokit/pull/735) The method `pid_hash`, which is used by compiled code to generate (hopefully) unique ids is now part of the public API.
-  - [#744](https://github.com/MichaelClerx/myokit/pull/744) Added a method `myokit.OpenCL.supported_and_available` to check if OpenCL is supported and at least one OpenCL device is detected.
-  - [#744](https://github.com/MichaelClerx/myokit/pull/744) Added a method `myokit.OpenCL.test_extension_on_current_platform(x)` that checks if OpenCL extension `x` is available on the currently selected platform.
+  - [#744](https://github.com/MichaelClerx/myokit/pull/744) Added a method `myokit.OpenCL.available` to check if OpenCL is supported and at least one OpenCL device is detected.
+  - [#754](https://github.com/MichaelClerx/myokit/pull/754) Added a method `myokit.OpenCL.current_info(x)` that returns information about the currently selected platform and device; support of OpenCL extensions can then be checked with `OpenCLPlatformInfo.has_extension`.
+  - [#754](https://github.com/MichaelClerx/myokit/pull/754) Improved error message when an `OpenCLSimulation` with double precision is run on a device that does not support it.
 - Changed
   - [#581](https://github.com/MichaelClerx/myokit/pull/581) Powers are now rendered without spaces in mmt code, e.g. `x^2` instead of `x ^ 2`.
   - [#595](https://github.com/MichaelClerx/myokit/pull/595) The `Simulation` class now uses CVODES instead of CVODE as backend, which may require changes to your installation.
@@ -48,6 +49,7 @@ This page lists the main changes made to Myokit in each release.
   - [#744](https://github.com/MichaelClerx/myokit/pull/744) Fixed a bug in `OpenCLSimulation.find_nan()`, that occurred for very small simulations (e.g. 2 cells).
   - [#744](https://github.com/MichaelClerx/myokit/pull/744) Fixed a bug in `OpenCLSimulation` that made it impossible to use `set_connections()` with double precision.
   - [#744](https://github.com/MichaelClerx/myokit/pull/744) Fixed a bug in `OpenCLSimulation` that made it impossible to use `set_connections()` on Python 2.
+  - [#754](https://github.com/MichaelClerx/myokit/pull/754) Improved error message when an `OpenCLSimulation` with double precision is run on a device that does not support it.
 
 ## [1.32.0] - 2021-01-19
 - Added
diff --git a/myokit/_sim/mcl.h b/myokit/_sim/mcl.h
index 256e6e422..65f94c0e1 100644
--- a/myokit/_sim/mcl.h
+++ b/myokit/_sim/mcl.h
@@ -262,7 +262,7 @@ mcl_flag(const cl_int flag)
  * Arguments:
  *  PyObject* platform  A string representing the platform, or None
  *  PyObject* device    A string representing the device, or None
- *  cl_platform_id* pid The returned cl_platform_id, or NULL
+ *  cl_platform_id* pid The returned cl_platform_id
  *  cl_device_id* did   The returned cl_device_id
  * The returned value is 0 if no error occurred, 1 if an error did occur. In
  *  this case a python error message will also be set.
@@ -349,7 +349,8 @@ int mcl_select_device(
             for (i=0; i<n_platforms; i++) {
                 flag = clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_GPU, 1, device_ids, &n_devices);
                 if(flag == CL_SUCCESS) {
-                    // Set selected device and return.
+                    // Set selected platform and device and return.
+                    *pid = platform_ids[i];
                     *did = device_ids[0];
                     return 0;
                 } else if(flag != CL_DEVICE_NOT_FOUND) {
@@ -361,7 +362,8 @@ int mcl_select_device(
             for (i=0; i<n_platforms; i++) {
                 flag = clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_ALL, 1, device_ids, &n_devices);
                 if(flag == CL_SUCCESS) {
-                    // Set selected device and return.
+                    // Set selected platform and device and return.
+                    *pid = platform_ids[i];
                     *did = device_ids[0];
                     return 0;
                 } else if(flag != CL_DEVICE_NOT_FOUND) {
@@ -385,7 +387,8 @@ int mcl_select_device(
                         if(mcl_flag(flag)) return 1;
                         trim(name);
                         if (strcmp(name, dname) == 0) {
-                            // Set selected device and return.
+                            // Set selected platform device and return.
+                            *pid = platform_ids[i];
                             *did = device_ids[j];
                             return 0;
                         }
@@ -492,34 +495,271 @@ mcl_round_total_size(const int ws_size, const int total_size)
     return size;
 }
 
-/* Memory used by mcl_device_info */
-static PyObject* platforms = NULL;  // Tuple of platform dicts
-static PyObject* platform = NULL;   // Temporary platform dict
-static PyObject* devices = NULL;    // Temporary tuple of devices
-static PyObject* device = NULL;     // Temporary device dict
-static PyObject* items = NULL;      // Temporary tuple of item sizes
-static PyObject* val;               // Temporary dictionary value
-static size_t* work_item_sizes;     // Temporary array of work item sizes
+/*
+ * Checks whether a given platform supports a given extension.
+ *
+ * Arguments:
+ *  cl_platform_id platform_id  The id of the platform to query
+ *  char* extension             The extension name, as a string.
+ *
+ * Returns 1 if support is detected, else 0.
+ */
+int mcl_platform_supports_extension(cl_platform_id platform_id, const char* extension)
+{
+    // Return from OpenCL
+    cl_int flag;
+
+    // String buffer
+    char buffer[65536];
+
+    // Extensions
+    #ifdef MYOKIT_DEBUG
+    printf("Querying extensions\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_EXTENSIONS, sizeof(buffer), buffer, NULL);
+    if(mcl_flag(flag)) return 0;
+    return (strstr(buffer, extension) != NULL);
+}
 
 /*
- * Tidies up if an error occurs in mcl_device_info
+ * Creates and returns a platform information dict, not including a devices
+ * entry.
+ *
+ * Arguments:
+ *  cl_platform_id platform_id  The id of the platform to query
+ *  char* buffer                An initialised string buffer, that can be used
+ *                              to read and write string information.
+ *
+ * Returns NULL and sets an error message if any exception occurs.
  */
-PyObject*
-mcl_device_info_clean()
+PyObject* mcl_info_platform_dict(cl_platform_id platform_id, size_t bufsize, char* buffer)
+{
+    // Return from OpenCL
+    cl_int flag;
+
+    // Platform dict (decref on exception)
+    PyObject* platform;
+
+    // Value to insert into the dict (decref immediatly after)
+    PyObject* val;
+
+    #ifdef MYOKIT_DEBUG
+    printf("Creating platform info dict\n");
+    #endif
+
+    // Create platform dict
+    platform = PyDict_New();
+    if(platform == NULL) {
+        PyErr_SetString(PyExc_Exception, "Unable to create dict for platform info.");
+        return NULL;
+    }
+
+    // Add profile
+    #ifdef MYOKIT_DEBUG
+    printf("  Querying profile\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_PROFILE, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(platform); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(platform, "profile", val);
+    Py_CLEAR(val);
+
+    // Version
+    #ifdef MYOKIT_DEBUG
+    printf("  Querying version\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_VERSION, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(platform); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(platform, "version", val);
+    Py_CLEAR(val);
+
+    // Name
+    #ifdef MYOKIT_DEBUG
+    printf("  Querying name\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(platform); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(platform, "name", val);
+    Py_CLEAR(val);
+
+    // Vendor
+    #ifdef MYOKIT_DEBUG
+    printf("  Querying vendor\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_VENDOR, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(platform); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(platform, "vendor", val);
+    Py_CLEAR(val);
+
+    // Extensions
+    #ifdef MYOKIT_DEBUG
+    printf("  Querying extensions\n");
+    #endif
+    flag = clGetPlatformInfo(platform_id, CL_PLATFORM_EXTENSIONS, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(platform); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(platform, "extensions", val);
+    Py_CLEAR(val);
+
+    // Finished!
+    return platform;
+}
+
+
+/*
+ * Creates and returns a device information dict, not including a devices
+ * entry.
+ *
+ * Arguments:
+ *  cl_device_id device_id  The id of the device to query.
+ *  size_t bufsize          Size of the string `buffer`.
+ *  char* buffer            An initialised string buffer, that can be used to
+ *                          read and write string information.
+ *
+ * Returns NULL and sets an error message if any exception occurs.
+ */
+PyObject* mcl_info_device_dict(cl_device_id device_id, size_t bufsize, char* buffer)
 {
-    Py_XDECREF(platforms); platforms = NULL;
-    Py_XDECREF(platform); platform = NULL;
-    Py_XDECREF(devices); devices = NULL;
-    Py_XDECREF(device); device = NULL;
-    Py_XDECREF(val); val = NULL;
+    // Return from OpenCL
+    cl_int flag;
+
+    // Device dict (decref on exception)
+    PyObject* device;
+
+    // Value to insert into the dict (decref immediately after)
+    PyObject* val;
+
+    // Placeholders for return values
+    cl_uint buf_uint;
+    cl_ulong buf_ulong;
+    size_t buf_size_t;
+
+    // Placeholder for work item sizes, must be freed before returning
+    size_t* work_item_sizes;
+
+    // Item sizes tuple to insert into the dict (decref immediately after)
+    PyObject* items_sizes_tuple;
+
+    // Iteration
+    size_t i;
+
+    #ifdef MYOKIT_DEBUG
+    printf("Creating device info dict\n");
+    #endif
+
+    // Create device dict
+    device = PyDict_New();
+    if(device == NULL) {
+        PyErr_SetString(PyExc_Exception, "Unable to create dict for device info.");
+        return NULL;
+    }
+
+    // Name
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_NAME, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(device, "name", val);
+    Py_CLEAR(val);
+
+    // Vendor
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_VENDOR, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(device, "vendor", val);
+    Py_CLEAR(val);
+
+    // Device version
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_VERSION, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(device, "version", val);
+    Py_CLEAR(val);
+
+    // Driver version
+    flag = clGetDeviceInfo(device_id, CL_DRIVER_VERSION, bufsize, buffer, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyUnicode_FromString(buffer);
+    PyDict_SetItemString(device, "driver", val);
+    Py_CLEAR(val);
+
+    // Clock speed (MHz)
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(buf_uint), &buf_uint, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_uint);
+    PyDict_SetItemString(device, "clock", val);
+    Py_CLEAR(val);
+
+    // Global memory (bytes)
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_ulong);
+    PyDict_SetItemString(device, "global", val);
+    Py_CLEAR(val);
+
+    // Local memory (bytes)
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_ulong);
+    PyDict_SetItemString(device, "local", val);
+    Py_CLEAR(val);
+
+    // Const memory (bytes)
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_ulong);
+    PyDict_SetItemString(device, "const", val);
+    Py_CLEAR(val);
+
+    // Computing units
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(buf_uint), &buf_uint, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_uint);
+    PyDict_SetItemString(device, "units", val);
+    Py_CLEAR(val);
+
+    // Max workgroup size
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(buf_size_t), &buf_size_t, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_size_t);
+    PyDict_SetItemString(device, "groups", val);
+    Py_CLEAR(val);
+
+    // Max workitem sizes
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(buf_uint), &buf_uint, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_uint);
+    PyDict_SetItemString(device, "dimensions", val);
+    Py_CLEAR(val);
+
+    work_item_sizes = (size_t*)malloc(buf_uint * sizeof(size_t));
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_WORK_ITEM_SIZES, buf_uint * sizeof(size_t), work_item_sizes, NULL);
+    if(mcl_flag(flag)) { free(work_item_sizes); Py_DECREF(device); return NULL; }
+    items_sizes_tuple = PyTuple_New((size_t)buf_uint);
+    for (i=0; i<buf_uint; i++) {
+        PyTuple_SetItem(items_sizes_tuple, i, PyLong_FromLong(work_item_sizes[i]));
+    }
     free(work_item_sizes); work_item_sizes = NULL;
-    return 0;
+    PyDict_SetItemString(device, "items", items_sizes_tuple);
+    Py_CLEAR(items_sizes_tuple);
+
+    // Maximum size of a kernel parameter
+    flag = clGetDeviceInfo(device_id, CL_DEVICE_MAX_PARAMETER_SIZE, sizeof(buf_size_t), &buf_size_t, NULL);
+    if(mcl_flag(flag)) { Py_DECREF(device); return NULL; }
+    val = PyLong_FromLong(buf_size_t);
+    PyDict_SetItemString(device, "param", val);
+    Py_CLEAR(val);
+
+    // Done!
+    return device;
 }
 
 /*
  * Returns information about the available OpenCL platforms and devices.
  *
- * Returns a reference to a tuple of platform dicts
+ * Returns a reference to a tuple of platform dicts.
  *
  * platforms = (
  *      dict(platform) {
@@ -551,211 +791,83 @@ mcl_device_info_clean()
  *     )
  */
 PyObject*
-mcl_device_info()
+mcl_info()
 {
-    // Array of platform ids
-    cl_platform_id platform_ids[MCL_MAX_PLATFORMS];
-
-    // Number of platforms
-    cl_uint n_platforms;
-
     // Return from OpenCL
     cl_int flag;
 
-    // Devices & return values from queries
+    // Array of platform ids, number of platforms
+    cl_platform_id platform_ids[MCL_MAX_PLATFORMS];
+    cl_uint n_platforms;
+
+    // Array of device ids, number of ids
     cl_device_id device_ids[MCL_MAX_DEVICES];
     cl_uint n_devices;
-    cl_uint buf_uint;
-    cl_ulong buf_ulong;
-    size_t wgroup_size;
-    size_t max_param;
 
-    // String buffer
+    // String buffer for returned strings
     char buffer[65536];
 
     // Iteration
-    cl_uint i, j, k;
+    cl_uint i, j;
 
-    // Set all pointers used by clean() to null
-    platforms = NULL;
-    platform = NULL;
-    devices = NULL;
-    device = NULL;
-    items = NULL;
-    val = NULL;
-    work_item_sizes = NULL;
+    // Python objects
+    PyObject* platforms;     // Tuple of platform dicts, to be returned
+    PyObject* platform;      // Temporary platform dict
+    PyObject* devices;       // Temporary tuple of devices
+    PyObject* device;        // Temporary device dict
 
     // Get platforms
     n_platforms = 0;
     flag = clGetPlatformIDs(MCL_MAX_PLATFORMS, platform_ids, &n_platforms);
-    if(mcl_flag(flag)) return mcl_device_info_clean();
+    if(mcl_flag(flag)) return NULL;
+    #ifdef MYOKIT_DEBUG
+    printf("Found %d platforms\n", n_platforms);
+    #endif
 
-    // Create platforms tuple
+    // Create platforms tuple, must decref on exception
     platforms = PyTuple_New((size_t)n_platforms);
 
+    // No platforms found
     if (n_platforms == 0) {
-        // No platforms found
         return platforms;
     }
 
     // Check all platforms
     for (i=0; i<n_platforms; i++) {
+
         // Create platform dict
-        platform = PyDict_New();
-
-        // Profile
-        flag = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_PROFILE, sizeof(buffer), buffer, NULL);
-        if(mcl_flag(flag)) return mcl_device_info_clean();
-        val = PyUnicode_FromString(buffer);
-        PyDict_SetItemString(platform, "profile", val);
-        Py_DECREF(val); val = NULL;
-
-        // Version
-        flag = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_VERSION, sizeof(buffer), buffer, NULL);
-        if(mcl_flag(flag)) return mcl_device_info_clean();
-        val = PyUnicode_FromString(buffer);
-        PyDict_SetItemString(platform, "version", val);
-        Py_DECREF(val); val = NULL;
-
-        // Name
-        flag = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_NAME, sizeof(buffer), buffer, NULL);
-        if(mcl_flag(flag)) return mcl_device_info_clean();
-        val = PyUnicode_FromString(buffer);
-        PyDict_SetItemString(platform, "name", val);
-        Py_DECREF(val); val = NULL;
-
-        // Vendor
-        flag = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_VENDOR, sizeof(buffer), buffer, NULL);
-        if(mcl_flag(flag)) return mcl_device_info_clean();
-        val = PyUnicode_FromString(buffer);
-        PyDict_SetItemString(platform, "vendor", val);
-        Py_DECREF(val); val = NULL;
-
-        // Extensions
-        flag = clGetPlatformInfo(platform_ids[i], CL_PLATFORM_EXTENSIONS, sizeof(buffer), buffer, NULL);
-        if(mcl_flag(flag)) return mcl_device_info_clean();
-        val = PyUnicode_FromString(buffer);
-        PyDict_SetItemString(platform, "extensions", val);
-        Py_DECREF(val); val = NULL;
-
-        // Devices
+        platform = mcl_info_platform_dict(platform_ids[i], sizeof(buffer), buffer);
+        if (platform == NULL) { Py_DECREF(platforms); return NULL; }
+
+        // Count devices
         flag = clGetDeviceIDs(platform_ids[i], CL_DEVICE_TYPE_ALL, MCL_MAX_DEVICES, device_ids, &n_devices);
         if (flag == CL_DEVICE_NOT_FOUND) {
             n_devices = 0;
         } else if(mcl_flag(flag)) {
-            return mcl_device_info_clean();
+            Py_DECREF(platforms);
+            return NULL;
         }
+
+        // Create devices tuple, must decref on exception
         devices = PyTuple_New((size_t)n_devices);
 
+        // Add devices
         for (j=0; j<n_devices; j++) {
-            // Create device dict
-            device = PyDict_New();
-
-            // Name
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_NAME, sizeof(buffer), buffer, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyUnicode_FromString(buffer);
-            PyDict_SetItemString(device, "name", val);
-            Py_DECREF(val); val = NULL;
-
-            // Vendor
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_VENDOR, sizeof(buffer), buffer, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyUnicode_FromString(buffer);
-            PyDict_SetItemString(device, "vendor", val);
-            Py_DECREF(val); val = NULL;
-
-            // Device version
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_VERSION, sizeof(buffer), buffer, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyUnicode_FromString(buffer);
-            PyDict_SetItemString(device, "version", val);
-            Py_DECREF(val); val = NULL;
-
-            // Driver version
-            flag = clGetDeviceInfo(device_ids[j], CL_DRIVER_VERSION, sizeof(buffer), buffer, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyUnicode_FromString(buffer);
-            PyDict_SetItemString(device, "driver", val);
-            Py_DECREF(val); val = NULL;
-
-            // Clock speed (MHz)
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_CLOCK_FREQUENCY, sizeof(buf_uint), &buf_uint, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_uint);
-            PyDict_SetItemString(device, "clock", val);
-            Py_DECREF(val); val = NULL;
-
-            // Global memory (bytes)
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_ulong);
-            PyDict_SetItemString(device, "global", val);
-            Py_DECREF(val); val = NULL;
-
-            // Local memory (bytes)
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_LOCAL_MEM_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_ulong);
-            PyDict_SetItemString(device, "local", val);
-            Py_DECREF(val); val = NULL;
-
-            // Const memory (bytes)
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, sizeof(buf_ulong), &buf_ulong, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_ulong);
-            PyDict_SetItemString(device, "const", val);
-            Py_DECREF(val); val = NULL;
-
-            // Computing units
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_COMPUTE_UNITS, sizeof(buf_uint), &buf_uint, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_uint);
-            PyDict_SetItemString(device, "units", val);
-            Py_DECREF(val); val = NULL;
-
-            // Max workgroup size
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(wgroup_size), &wgroup_size, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(wgroup_size);
-            PyDict_SetItemString(device, "groups", val);
-            Py_DECREF(val); val = NULL;
-
-            // Max workitem sizes
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(buf_uint), &buf_uint, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(buf_uint);
-            PyDict_SetItemString(device, "dimensions", val);
-            Py_DECREF(val); val = NULL;
-
-            work_item_sizes = (size_t*)malloc(buf_uint * sizeof(size_t));
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_WORK_ITEM_SIZES, buf_uint*sizeof(size_t), work_item_sizes, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            items = PyTuple_New((size_t)buf_uint);
-            for (k=0; k<buf_uint; k++) {
-                PyTuple_SetItem(items, k, PyLong_FromLong(work_item_sizes[k]));
-            }
-            free(work_item_sizes); work_item_sizes = NULL;
-            PyDict_SetItemString(device, "items", items);
-            Py_DECREF(items); items = NULL;
-
-            // Maximum size of a kernel parameter
-            flag = clGetDeviceInfo(device_ids[j], CL_DEVICE_MAX_PARAMETER_SIZE, sizeof(max_param), &max_param, NULL);
-            if(mcl_flag(flag)) return mcl_device_info_clean();
-            val = PyLong_FromLong(max_param);
-            PyDict_SetItemString(device, "param", val);
-            Py_DECREF(val); val = NULL;
-
-            // Add device to devices tuple
+
+            // Create device dict, must decref on exception
+            device = mcl_info_device_dict(device_ids[j], sizeof(buffer), buffer);
+            if (device == NULL) { Py_DECREF(platforms); Py_DECREF(devices); return NULL; }
+
+            // Add device to devices tuple (steals reference)
             PyTuple_SetItem(devices, j, device);
             device = NULL;
         }
 
         // Add devices entry to platform dict
         PyDict_SetItemString(platform, "devices", devices);
-        Py_DECREF(devices); devices = NULL;
+        Py_CLEAR(devices);
 
-        // Add platform to platforms tuple
+        // Add platform to platforms tuple (steals reference)
         PyTuple_SetItem(platforms, i, platform);
         platform = NULL;
     }
@@ -764,5 +876,56 @@ mcl_device_info()
     return platforms;
 }
 
+/*
+ * Returns information about the currently selected or default OpenCL platform
+ * and device.
+ *
+ * Arguments:
+ *  PyObject* platform_name  A string naming the selected platform, or None
+ *  PyObject* device_name    A string naming the selected device, or None
+ *
+ * Returns: a platform dict similar to those returned in a tuple by mcl_info,
+ * but with a single "device" entry instead of a "devices" list.
+ */
+PyObject*
+mcl_info_current(
+    PyObject* platform_name,
+    PyObject* device_name)
+{
+    // The current platform and device id
+    cl_platform_id platform_id;
+    cl_device_id device_id;
+
+    // The platform and device dicts (may need decref on exception)
+    PyObject* platform;
+    PyObject* device;
+
+    // String buffer
+    char buffer[65536];
+
+    // Get platform and device id
+    if (mcl_select_device(platform_name, device_name, &platform_id, &device_id)) {
+        return NULL;
+    }
+
+    #ifdef MYOKIT_DEBUG
+    printf("Found platform %d\n", platform_id);
+    printf("Found device %d\n", device_id);
+    #endif
+
+    // Create platform dict
+    platform = mcl_info_platform_dict(platform_id, sizeof(buffer), buffer);
+    if (platform == NULL) return NULL;
+
+    // Add device dict
+    device = mcl_info_device_dict(device_id, sizeof(buffer), buffer);
+    if (device == NULL) { Py_DECREF(platform); return NULL; }
+    PyDict_SetItemString(platform, "device", device);
+    Py_DECREF(device);
+
+    // Return platform dict
+    return platform;
+}
+
 #undef MyokitOpenCL
 #endif
diff --git a/myokit/_sim/opencl.c b/myokit/_sim/opencl.c
index c3c71f03f..af208c455 100644
--- a/myokit/_sim/opencl.c
+++ b/myokit/_sim/opencl.c
@@ -23,22 +23,39 @@ tab = '    '
 #include "mcl.h"
 
 /*
- * Returns a tuple with information about the available opencl platforms and
+ * Returns a tuple with information about the available OpenCL platforms and
  * devices.
  */
 static PyObject*
 info(PyObject *self, PyObject *args)
 {
-    mcl_device_info();
-    return mcl_device_info();
+    return mcl_info();
+}
+
+
+/*
+ * Returns a tuple with information about the selected OpenCL platform and
+ * device.
+ */
+static PyObject*
+current(PyObject* self, PyObject* args)
+{
+    // Platform and device name (Python bytes)
+    PyObject *platform_name;
+    PyObject *device_name;
+
+    // Check input arguments
+    if(!PyArg_ParseTuple(args, "OO", &platform_name, &device_name)) {
+        // Nothing allocated yet, no pyobjects _created_, return directly
+        PyErr_SetString(PyExc_Exception, "Wrong number of arguments.");
+        return 0;
+    }
+
+    return mcl_info_current(platform_name, device_name);
 }
 
 /*
- * Tests building a program and returns the
- *
- * If the program builds successfully, None is returned. If the program fails
- * to compile a string containing the compiler output is returned. If something
- * else goes wrong an exception is raised.
+ * Tests building a program and returns the output.
  */
 static PyObject*
 build(PyObject* self, PyObject* args)
@@ -124,8 +141,9 @@ build(PyObject* self, PyObject* args)
  * Methods in this module
  */
 static PyMethodDef SimMethods[] = {
-    {"info", info, METH_VARARGS, "Get some information about OpenCL devices."},
     {"build", build, METH_VARARGS, "Try building an OpenCL kernel program."},
+    {"current", current, METH_VARARGS, "Get information about the currently selected OpenCL platform and device."},
+    {"info", info, METH_NOARGS, "Get information about available OpenCL platforms and devices."},
     {NULL},
 };
 
diff --git a/myokit/_sim/opencl.py b/myokit/_sim/opencl.py
index bb5467bf3..5dc683254 100644
--- a/myokit/_sim/opencl.py
+++ b/myokit/_sim/opencl.py
@@ -72,18 +72,6 @@ def __init__(self):
             OpenCL._instance = False
             OpenCL._message = str(e)
 
-    @staticmethod
-    def info(formatted=False):
-        """
-        Queries the OpenCL installation for the available platforms and
-        devices and returns a :class:`myokit.OpenCLInfo` object.
-
-        If ``formatted=True`` is set, a formatted version of the information is
-        returned instead.
-        """
-        info = OpenCLInfo(OpenCL._get_instance().info())
-        return info.format() if formatted else info
-
     @staticmethod
     def _get_instance():
         """
@@ -100,6 +88,44 @@ def _get_instance():
         # Return instance
         return OpenCL._instance
 
+    @staticmethod
+    def available():
+        """
+        Returns ``True`` if OpenCL support has been detected on this sytem and
+        at least one platform and device were detected.
+        """
+        try:
+            OpenCL.current_info()
+        except Exception:
+            return False
+        return True
+
+    @staticmethod
+    def current_info(formatted=False):
+        """
+        Returns a :class:`myokit.OpenCLPlatformInfo` object for the platform
+        and device selected by the user, or chosen as default.
+
+        If ``formatted=True`` is set, a formatted version of the information is
+        returned instead.
+        """
+        platform, device = OpenCL.load_selection_bytes()
+        info = OpenCL._get_instance().current(platform, device)
+        info = OpenCLPlatformInfo(info)
+        return info.format() if formatted else info
+
+    @staticmethod
+    def info(formatted=False):
+        """
+        Queries the OpenCL installation for the available platforms and
+        devices and returns a :class:`myokit.OpenCLInfo` object.
+
+        If ``formatted=True`` is set, a formatted version of the information is
+        returned instead.
+        """
+        info = OpenCLInfo(OpenCL._get_instance().info())
+        return info.format() if formatted else info
+
     @staticmethod
     def load_selection():
         """
@@ -213,32 +239,12 @@ def supported():
         except NoOpenCLError:
             return False
 
+    '''
     @staticmethod
-    def supported_and_available():
+    def test_build(code):
         """
-        Returns ``True`` if OpenCL support has been detected on this sytem and
-        at least one (platform and) device were detected.
-        """
-        try:
-            return OpenCL.info().device_available()
-        except NoOpenCLError:
-            return False
-
-    @staticmethod
-    def test_extension_on_current_platform(extension, raw=False):
-        """
-        Tries building a program on the currently selected platform and device
-        (or the defaults, if none are explicitly selected) that does nothing
-        except enabling the given ``extension``.
-
-        By default, the method returns ``True`` if the name of the extension
-        does not appear in the compiler output (which is taken to indicate a
-        warning).
-        This can be changed by setting ``raw=True``, in which case the raw
-        compiler output will be returned.
-
-        This method can be used to test if an exception is available on the
-        selected or default device (rather than querying a specific device).
+        Tries building a kernel program on the currently selected platform and
+        device and returns the compiler output.
         """
         try:
             cl = OpenCL._get_instance()
@@ -248,24 +254,19 @@ def test_extension_on_current_platform(extension, raw=False):
         # Get preferred platform/device combo from configuration file
         platform, device = myokit.OpenCL.load_selection_bytes()
 
-        # Create code and run
-        code = '#pragma OPENCL EXTENSION ' + str(extension) + ' : enable\n'
-        out = cl.build(platform, device, code)
-
-        # Check output and return
-        if raw:
-            return out
-        return extension not in out
+        # Build and return
+        return cl.build(platform, device, code)
+    '''
 
 
 class OpenCLInfo(object):
     """
     Represents information about the available OpenCL platforms and devices.
 
-    Each ``OpenCLInfo`` object has a property ``platforms``, containing a list
-    (actually a tuple) of :class:`OpenCLPlatformInfo` objects.
+    Each ``OpenCLInfo`` object has a property ``platforms``, containing a tuple
+    of :class:`OpenCLPlatformInfo` objects.
 
-    ``OpenCLInfo`` objects can be created by any OpenCL enabled part of Myokit.
+    ``OpenCLInfo`` objects are created and returned by :class:`myokit.OpenCL`.
     """
     def __init__(self, mcl_info):
         # mcl_info is a python object returned by mcl_device_info (mcl.h)
@@ -273,44 +274,13 @@ def __init__(self, mcl_info):
 
     def format(self):
         """
-        Returns a formatted version of the information.
+        Returns a formatted string version of this object's information.
         """
-        t = []
+        b = []
         for i, platform in enumerate(self.platforms):
-            t.append('Platform ' + str(i))
-            t.append(' Name       : ' + platform.name)
-            t.append(' Vendor     : ' + platform.vendor)
-            t.append(' Version    : ' + platform.version)
-            t.append(' Profile    : ' + platform.profile)
-            t.append(' Extensions : ' + platform.extensions)
-            t.append(' Devices    :')
-            for j, device in enumerate(platform.devices):
-                t.append('  Device ' + str(j))
-                t.append('   Name            : ' + device.name)
-                t.append('   Vendor          : ' + device.vendor)
-                t.append('   Version         : ' + device.version)
-                t.append('   Driver          : ' + device.driver)
-                t.append('   Clock speed     : ' + str(device.clock) + ' MHz')
-                t.append('   Global memory   : ' + bytesize(device.globl))
-                t.append('   Local memory    : ' + bytesize(device.local))
-                t.append('   Constant memory : ' + bytesize(device.const))
-                t.append(
-                    '   Max param size  : ' + str(device.param) + ' bytes')
-                t.append('   Max work groups : ' + str(device.groups))
-                t.append(
-                    '   Max work items  : ['
-                    + ', '.join([str(x) for x in device.items]) + ']')
-        return '\n'.join(t)
-
-    def device_available(self):
-        """
-        Returns ``True`` only if at least one platform and one device are
-        available.
-        """
-        for platform in self.platforms:
-            for device in platform.devices:
-                return True
-        return False
+            b.append('Platform ' + str(i))
+            platform._format(b, pre=' ')
+        return '\n'.join(b)
 
 
 class OpenCLPlatformInfo(object):
@@ -330,8 +300,13 @@ class OpenCLPlatformInfo(object):
     ``extensions`` (string)
         The available OpenCL extensions on this platform.
     ``devices`` (tuple)
-        A tuple of device information dicts for the devices available on
-        this platform.
+        A tuple of device information objects for the devices available on
+        this platform. This field may be ``None``, in which case ``device``
+        will be set instead.
+    ``device`` (OpenCLDeviceInfo)
+        An information objects for the device selected by the user, or chosen
+        as the default device. This field may be ``None``, in which case
+        ``devices`` will be set instead.
 
     ``OpenCLPlatformInfo`` objects are created as part of a :class:`OpenCLInfo`
     objects, as returned by most OpenCL enabled parts of Myokit.
@@ -341,9 +316,45 @@ def __init__(self, platform):
         self.vendor = platform['vendor'].strip()
         self.version = platform['version'].strip()
         self.profile = platform['profile'].strip()
-        self.extensions = platform['extensions'].strip()
-        self.devices = tuple(
-            [OpenCLDeviceInfo(x) for x in platform['devices']])
+        self.extensions = platform['extensions'].strip().split()
+        self.devices = platform.get('devices', None)
+        if self.devices is not None:
+            self.devices = tuple([OpenCLDeviceInfo(x) for x in self.devices])
+        self.device = platform.get('device', None)
+        if self.device is not None:
+            self.device = OpenCLDeviceInfo(self.device)
+
+    def format(self):
+        """
+        Returns a formatted string version of this object's information.
+        """
+        b = []
+        b.append('Platform: ' + self.name)
+        self._format(b, ' ', name=False)
+        return '\n'.join(b)
+
+    def _format(self, b, pre='', name=True):
+        """
+        Formats the information in this object and adds it to the list ``b``.
+        """
+        if name:
+            b.append(pre + 'Name       : ' + self.name)
+        b.append(pre + 'Vendor     : ' + self.vendor)
+        b.append(pre + 'Version    : ' + self.version)
+        b.append(pre + 'Profile    : ' + self.profile)
+        b.append(pre + 'Extensions : ' + ' '.join(self.extensions))
+        if self.devices is not None:
+            b.append(pre + 'Devices:')
+            for j, device in enumerate(self.devices):
+                b.append(pre + ' Device ' + str(j))
+                device._format(b, pre + '  ')
+
+        if self.device is not None:
+            b.append(pre[:-1] + 'Device: ' + self.device.name)
+            self.device._format(b, pre, name=False)
+
+    def has_extension(self, extension):
+        return extension in self.extensions
 
 
 class OpenCLDeviceInfo(object):
@@ -397,6 +408,24 @@ def __init__(self, device):
         self.groups = device['groups']
         self.items = tuple(device['items'])
 
+    def _format(self, b, pre='', name=True):
+        """
+        Formats the information in this object and adds it to the list ``b``.
+        """
+        if name:
+            b.append(pre + 'Name            : ' + self.name)
+        b.append(pre + 'Vendor          : ' + self.vendor)
+        b.append(pre + 'Version         : ' + self.version)
+        b.append(pre + 'Driver          : ' + self.driver)
+        b.append(pre + 'Clock speed     : ' + str(self.clock) + ' MHz')
+        b.append(pre + 'Global memory   : ' + bytesize(self.globl))
+        b.append(pre + 'Local memory    : ' + bytesize(self.local))
+        b.append(pre + 'Constant memory : ' + bytesize(self.const))
+        b.append(pre + 'Max param size  : ' + str(self.param) + ' bytes')
+        b.append(pre + 'Max work groups : ' + str(self.groups))
+        b.append(pre + 'Max work items  : [' +
+                 ', '.join([str(x) for x in self.items]) + ']')
+
 
 def bytesize(size):
     """
diff --git a/myokit/_sim/openclsim.c b/myokit/_sim/openclsim.c
index e4d9cf374..8e5f66bda 100644
--- a/myokit/_sim/openclsim.c
+++ b/myokit/_sim/openclsim.c
@@ -38,7 +38,11 @@ tab = '    '
 
 #define n_state <?= str(model.count_states()) ?>
 
-typedef <?= ('float' if precision == myokit.SINGLE_PRECISION else 'double') ?> Real;
+typedef <?= ('double' if precision == myokit.DOUBLE_PRECISION else 'float') ?> Real;
+<?
+if precision == myokit.DOUBLE_PRECISION:
+    print('#define MYOKIT_DOUBLE_PRECISION')
+?>
 
 /*
  * Adds a variable to the logging lists. Returns 1 if successful.
@@ -587,15 +591,17 @@ sim_init(PyObject* self, PyObject* args)
     }
     #ifdef MYOKIT_DEBUG
     printf("Selected platform and device id.\n");
-    if (platform_id == NULL) {
-        printf("No preferred platform set.\n");
-    } else {
-        printf("Preferred platform set.\n");
+    #endif
+
+    // Query capabilities
+    #ifdef MYOKIT_DOUBLE_PRECISION
+    if (!mcl_platform_supports_extension(platform_id, "cl_khr_fp64")) {
+        PyErr_SetString(PyExc_Exception, "The OpenCL extension cl_khr_fp64 is required for double precision simulations, but was not found on the current OpenCL platform/device.");
+        return sim_clean();
     }
-    if (device_id == NULL) {
-        printf("No preferred device set.\n");
-    } else {
-        printf("Preferred device set.\n");
+    if ((connections != Py_None) && (!mcl_platform_supports_extension(platform_id, "cl_khr_int64_base_atomics"))) {
+        PyErr_SetString(PyExc_Exception, "The OpenCL extension cl_khr_int64_base_atomics is required for double precision simulations with set_connections(), but was not found on the current OpenCL platform/device.");
+        return sim_clean();
     }
     #endif
 
@@ -603,16 +609,8 @@ sim_init(PyObject* self, PyObject* args)
     #ifdef MYOKIT_DEBUG
     printf("Attempting to create OpenCL context...\n");
     #endif
-    if (platform_id != NULL) {
-        #ifdef MYOKIT_DEBUG
-        printf("Creating context with context_properties\n");
-        #endif
-        cl_context_properties context_properties[] =
-            { CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id, 0};
-        context = clCreateContext(context_properties, 1, &device_id, NULL, NULL, &flag);
-    } else {
-        context = clCreateContext(NULL, 1, &device_id, NULL, NULL, &flag);
-    }
+    cl_context_properties context_properties[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform_id, 0};
+    context = clCreateContext(context_properties, 1, &device_id, NULL, NULL, &flag);
     if(mcl_flag2("context", flag)) return sim_clean();
     #ifdef MYOKIT_DEBUG
     printf("Created context.\n");
@@ -984,7 +982,7 @@ sim_step(PyObject *self, PyObject *args)
             for(i=0; i<n_vars; i++) {
                 flt = PyFloat_FromDouble(*vars[i]);
                 ret = PyObject_CallMethodObjArgs(logs[i], list_update_str, flt, NULL);
-                Py_DECREF(flt); flt = NULL;
+                Py_CLEAR(flt);
                 Py_XDECREF(ret);
                 if(ret == NULL) {
                     PyErr_SetString(PyExc_Exception, "Call to append() failed on logging list.");


Test patch:
diff --git a/myokit/tests/shared.py b/myokit/tests/shared.py
index 27bc4bd2e..04860ca23 100644
--- a/myokit/tests/shared.py
+++ b/myokit/tests/shared.py
@@ -31,10 +31,15 @@
 DIR_FORMATS = os.path.join(DIR_DATA, 'formats')
 
 # OpenCL support
-OpenCL_FOUND = myokit.OpenCL.supported_and_available()
-OpenCL_DOUBLE_PRECISION_CONNECTIONS = (
-    OpenCL_FOUND and myokit.OpenCL.test_extension_on_current_platform(
-        'cl_khr_int64_base_atomics'))
+OpenCL_FOUND = myokit.OpenCL.available()
+OpenCL_DOUBLE_PRECISION = False
+OpenCL_DOUBLE_PRECISION_CONNECTIONS = False
+if OpenCL_FOUND:
+    info = myokit.OpenCL.current_info()
+    OpenCL_DOUBLE_PRECISION = info.has_extension('cl_khr_fp64')
+    OpenCL_DOUBLE_PRECISION_CONNECTIONS = info.has_extension(
+        'cl_khr_int64_base_atomics')
+    del(info)
 
 
 class TemporaryDirectory(object):
diff --git a/myokit/tests/test_simulation_opencl.py b/myokit/tests/test_simulation_opencl.py
index 2a6f502dd..51ea77961 100755
--- a/myokit/tests/test_simulation_opencl.py
+++ b/myokit/tests/test_simulation_opencl.py
@@ -14,9 +14,13 @@
 
 import myokit
 
-from shared import OpenCL_FOUND, OpenCL_DOUBLE_PRECISION_CONNECTIONS
-from shared import DIR_DATA
-from shared import WarningCollector
+from shared import (
+    DIR_DATA,
+    OpenCL_FOUND,
+    OpenCL_DOUBLE_PRECISION,
+    OpenCL_DOUBLE_PRECISION_CONNECTIONS,
+    WarningCollector,
+)
 
 # Unit testing in Python 2 and 3
 try:
@@ -34,6 +38,10 @@ class SimulationOpenCL0dTest(unittest.TestCase):
     """
     Tests the OpenCL simulation against CVODE, in 0d mode.
     """
+
+    @unittest.skipIf(
+        not OpenCL_DOUBLE_PRECISION,
+        'OpenCL double precision extension not supported on selected device.')
     def test_against_cvode(self):
         # Compare the SimulationOpenCL output with CVODE output
 
@@ -949,6 +957,9 @@ def test_basic(self):
         self.assertIn('0.0.ica.ICa', logt)
         self.assertIn(str(ntx - 1) + '.' + str(nty - 1) + '.ica.ICa', logt)
 
+    @unittest.skipIf(
+        not OpenCL_DOUBLE_PRECISION,
+        'OpenCL double precision extension not supported on selected device.')
     def test_against_cvode(self):
         # Compare the fiber-tissue simulation output with CVODE output