diff --git a/doc/nestml_language/neurons_in_nestml.rst b/doc/nestml_language/neurons_in_nestml.rst index b7a0ec39c..ce96d98e5 100644 --- a/doc/nestml_language/neurons_in_nestml.rst +++ b/doc/nestml_language/neurons_in_nestml.rst @@ -141,6 +141,8 @@ Note that in this example, the intended physical unit (pA) was assigned by multi (Re)setting synaptic integration state ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +XXXXXXXXXXXXXX: this needs to be rewritten + When convolutions are used, additional state variables are required for each pair *(shape, spike input port)* that appears as the parameters in a convolution. These variables track the dynamical state of that kernel, for that input port. The number of variables created corresponds to the dimensionality of the kernel. For example, in the code block above, the one-dimensional kernel ``G`` is used in a convolution with spiking input port ``spikes``. During code generation, a new state variable called ``G__conv__spikes`` is created for this combination, by joining together the name of the kernel with the name of the spike buffer using (by default) the string “__conv__”. If the same kernel is used later in a convolution with another spiking input port, say ``spikes_GABA``, then the resulting generated variable would be called ``G__conv__spikes_GABA``, allowing independent synaptic integration between input ports but allowing the same kernel to be used more than once. The process of generating extra state variables for keeping track of convolution state is normally hidden from the user. For some models, however, it might be required to set or reset the state of synaptic integration, which is stored in these internally generated variables. For example, we might want to set the synaptic current (and its rate of change) to 0 when firing a dendritic action potential. Although we would like to set the generated variable ``G__conv__spikes`` to 0 in the running example, a variable by this name is only generated during code generation, and does not exist in the namespace of the NESTML model to begin with. To still allow referring to this state in the context of the model, it is recommended to use an inline expression, with only a convolution on the right-hand side. diff --git a/doc/running/running_nest.rst b/doc/running/running_nest.rst index 7c9db4b2c..b8d5efc5c 100644 --- a/doc/running/running_nest.rst +++ b/doc/running/running_nest.rst @@ -110,13 +110,13 @@ After generating and building the model code, a ``receptor_type`` entry is avail Note that in multisynapse neurons, receptor ports are numbered starting from 1. -We furthermore wish to record the synaptic currents ``I_kernel1``, ``I_kernel2`` and ``I_kernel3``. During code generation, one buffer is created for each combination of (kernel, spike input port) that appears in convolution statements. These buffers are named by joining together the name of the kernel with the name of the spike buffer using (by default) the string "__X__". The variables to be recorded are thus named as follows: +We furthermore wish to record the synaptic currents ``I_kernel1``, ``I_kernel2`` and ``I_kernel3``. During code generation, one buffer is created for each combination of (kernel, spike input port) that appears in convolution statements. These buffers are named by joining together the name of the kernel with the name of the spike buffer using (by default) the string "__conv__". The variables to be recorded are thus named as follows: XXX: add reference to the part of the docs that describe convolutions .. code-block:: python - mm = nest.Create('multimeter', params={'record_from': ['I_kernel1__X__spikes_1', - 'I_kernel2__X__spikes_2', - 'I_kernel3__X__spikes_3'], + mm = nest.Create('multimeter', params={'record_from': ['I_kernel1__conv__spikes_1', + 'I_kernel2__conv__spikes_2', + 'I_kernel3__conv__spikes_3'], 'interval': .1}) nest.Connect(mm, neuron) diff --git a/doc/tutorials/active_dendrite/nestml_active_dendrite_tutorial.ipynb b/doc/tutorials/active_dendrite/nestml_active_dendrite_tutorial.ipynb index 78345262e..8e664dfe7 100644 --- a/doc/tutorials/active_dendrite/nestml_active_dendrite_tutorial.ipynb +++ b/doc/tutorials/active_dendrite/nestml_active_dendrite_tutorial.ipynb @@ -1,6 +1,7 @@ { "cells": [ { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -66,6 +67,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -182,6 +184,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -290,6 +293,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -406,7 +410,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -422,6 +426,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -444,7 +449,7 @@ }, { "data": { - "image/png": "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\n", + "image/png": "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", "text/plain": [ "
" ] @@ -460,6 +465,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -486,7 +492,7 @@ " ...\n", "```\n", "\n", - "In order to ignore presynaptic input that arrives during and before a dendritic action potential, we use the inline aliasing feature of NESTML. Usually, synaptic integration is expressed as a convolution, for example:\n", + "In order to ignore presynaptic input that arrives during and before a dendritic action potential, we use the inline aliasing feature of NESTML. Usually, synaptic integration is expressed as a convolution, for example: XXXXXXXXXX: this needs to be rewritten\n", "\n", "```nestml\n", "equations:\n", @@ -591,6 +597,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -699,6 +706,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -719,7 +727,7 @@ }, { "data": { - "image/png": "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1n2+9dZbLFu2jF9//ZWJEyda7+/WrRsvvfQSAI0aNWLr1q189NFHxSZzixcvxmw2880331j7usybNw9/f382bNhAv3797PL6MzMzycjI4K677iImJgaApk2bWh9/9NFHefzxx5k5cyZubm7s27ePQ4cOsXz5cpv93Kh+KpNK+swJYaPgkyAtc0JIMldmHloNR9/sX6qyZrOZrMwsfHx97LLElIe29KsKnDp1itzc3CIJlV6vp23bttbbrVq1sv5dsKZoamoqderUITs7m2nTpvHHH3+QlJSE0WgkLy+PhIQEm30WJIvX3y5phOuBAwc4deoUPj4+Nvfn5+dz+vTpUr++mwkMDGT06NH079+fvn370qdPH4YPH259jffeey8TJkxg2bJl3H///cyfP59evXpRr149m/3cqH4qk1pGswpho3AFCMnmhJBkroxUKlWpL3WazWaMrho8XV0qfb3Q7OxsAP744w9q1apl85ibm5s1cdJqtdb7Cw6KZrMZgOeff541a9bwwQcf0KBBAzw8PLjvvvvQ6/W3FFf79u354Ycfijx2/eVfe5g3bx5PP/00K1euZPHixbz66qusWbOGLl264OrqyiOPPMK8efMYOnQoCxcu5JNPPimyjxvVT2WyjmaVIRBCADIAQojrSTJXTTVr1gw3NzcSEhKslwmvV5pWsK1btzJ69GiGDBkCWBKx4gYq7Nixo8jt6y9pXq9du3YsXryY0NBQfH19S/FKbk3btm1p27YtU6dOJTY2loULF9KlSxfAcqm1RYsWfPHFFxiNRoYOHVrh8ZSbNZF0cBxCVBEF/UdlnjkhZDRrteXj48Pzzz/Ps88+y4IFCzh9+jT79u3js88+Y8GCBaXaR8OGDVm6dCn79+/nwIEDPPDAA8W2Sm3dupX33nuPEydOMGvWLH7++WeeeeaZYvf54IMPEhwczD333MPmzZuJj49nw4YNPP300yQmJt7Sa75efHw8U6dOZfv27Zw7d47Vq1dz8uRJmySzadOmdOnShSlTpjBy5Eg8PDzs9vz2ppbRrELYKBwAIYSQlrlq7K233iIkJIQZM2Zw5swZ/P39adeuHS+//HKpLhXOnDmTsWPH0rVrV4KDg5kyZQqZmZlFyj333HPs2bOH6dOn4+vry8yZM+nfv/h+hZ6enmzatIkpU6YwdOhQsrKyqFWrFr1797ZrS52n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5JNP6Nu3r8M7m4viubu7M2XKFF588UVcXV3p1q0bly5d4siRIzzyyCPUrl2badOm8c4773DixAk+/PDDcj/XlClT6NKlCxMnTuTRRx/Fy8uLo0ePsmbNGj7//PNS7SM0NBQPDw9WrlxJVFQU7u7u+Pn5lTum6kaRljkhbKikz5wQVmXOxOrWrcuWLVvYtGkTJ06cqIiYhJ289tprPPfcc7z++us0bdqUESNGkJqailarZdGiRcTFxdGqVSveffdd3n777XI/T6tWrdi4cSMnTpzgtttuo23btrz++utERkaWeh8uLi58+umnfPXVV0RGRnLPPfeUO57qSPrMCWFL+swJUajMLXNxcXHWvnIdO3akUaNGPPTQQ4CcaKoatVrNK6+8wiuvvFLksW7duhVZnu36y96jR49m9OjRRbY7e/Zssc/VsWNHVq9eXWIsxW33z0u7jz76aJlG29YkJrOMZhXiegWt1NV1VRwhyqJc10i7devG3LlzSUpK4vHHH+fnn3/GZDLx5JNP8vXXX3Pp0iV7xylEjSaXWYWwJVOTCFHoljq8eXt7M378eLZt28aRI0do164dr776apkurwkhbs4s88wJYUMGQAhRyG6jF5o2bcqHH35IYmIiixcvttduhRBInzkh/qnwMquDAxGiCrileeZMJhPLli3j2LFjADRr1ox77rmHoUOH2iU4IYRFwaUkjSRzQgDSMifE9cqdzB05coS7776b5ORkGjduDMC7775LSEgIv//+O82bN7dbkFWBdLIVpVUR7xXrcl4yE5AQgLTMCXG9cp8aHn30UZo3b05iYiL79u1j3759nD9/nlatWjF+/Hh7xuhQBasW5ObmOjgS4SwK3iv2XDy6oGVOLrMKYSEtc0IUKnfL3P79+9mzZ491BQGAgIAA3nnnHTp27GiX4KoCjUaDv7+/dQ1RT0/PUp9QzWYzer2e/Px8mVy5glWFulYUhdzcXFJTU/H390ej0dht34VTk0gyJwTIaFYhrlfuZK5Ro0akpKQUuZyampp6S8s6VUXh4eEAZV4UXlEU8vLySlzjVNhPVaprf39/63vGXmQ0qxC2ZNJgIQqVO5mbMWMGTz/9NNOmTaNLly4A7NixgzfffJN3332XzMxMa1lfX99bj9SBVCoVERERhIaGFrsgfUkMBgObNm2iR48edr3kJoqqKnWt1Wrt2iJX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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnMAAAG2CAYAAAAUfQCUAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/SrBM8AAAACXBIWXMAAA9hAAAPYQGoP6dpAACvSUlEQVR4nOzdd3xT5f7A8U+SpnvvljJK2XtDARFko6iAF8TFEq8KKuJAnOC4XBdOFK+yHCDKD8SBDNl7yqbMQil0AKW7zTy/P0JTYltoS9o07ff9epU2yXNOvnlIzvnmOc9QKYqiIIQQQgghnJLa0QEIIYQQQojyk2ROCCGEEMKJSTInhBBCCOHEJJkTQgghhHBikswJIYQQQjgxSeaEEEIIIZyYJHNCCCGEEE5MkjkhhBBCCCcmyZwQQgghhBOTZE4IIYQQwok5NJnbtGkTgwcPJjIyEpVKxS+//GLz+OjRo1GpVDY/AwYMsCmTlpbGgw8+iK+vL/7+/owbN47s7OxKfBVCCCGEcDb//e9/UalUTJo0yXpfz549i+Qdjz/+uM12CQkJ3HnnnXh6ehIaGsoLL7yA0Wis5OhtuTjyyXNycmjdujVjx45l6NChxZYZMGAA8+bNs952c3OzefzBBx8kKSmJNWvWYDAYGDNmDI899hgLFy6s0NiFEEII4Zx2797NV199RatWrYo8Nn78eN58803rbU9PT+vfJpOJO++8k/DwcLZt20ZSUhKPPPIIWq2W//znP5USe3EcmswNHDiQgQMH3rCMm5sb4eHhxT527NgxVq5cye7du+nQoQMAn332GYMGDeKDDz4gMjLS7jELIYQQwnllZ2fz4IMP8vXXX/P2228XedzT07PEvGP16tUcPXqUv/76i7CwMNq0acNbb73FlClTmDZtGq6urhUdfrEcmsyVxoYNGwgNDSUgIIA77riDt99+m6CgIAC2b9+Ov7+/NZED6NOnD2q1mp07dzJkyJBi96nT6dDpdNbbZrMZwO7/CYqiYDKZ0Gg0qFQqu+5b2JK6rjxS15VH6rpySX1XHnvWtU6nQ6VSoVYX9hxzc3MrciWvwIQJE7jzzjvp06dPscncDz/8wPfff094eDiDBw/mtddes7bObd++nZYtWxIWFmYt379/f5544gmOHDlC27Ztb+m1lFeVTuYGDBjA0KFDiY6O5vTp07z88ssMHDiQ7du3o9FoSE5OJjQ01GYbFxcXAgMDSU5OLnG/M2bMYPr06dbbHh4eLFq0qMJehxBCCCEqxqJFi1i8eLHNfW+88QbTpk0rUvbHH39k37597N69u9h9PfDAA9StW5fIyEgOHjzIlClTOH78OEuXLgUgOTnZJpEDrLdvlHdUtCqdzN1///3Wv1u2bEmrVq2IiYlhw4YN9O7du9z7nTp1KpMnT7berqiWOaPRyPr16+nVqxcuLlW6qp2e1HXlkbquPFLXlUvqu/LYs6579OjB7Nmzi7TM/dP58+d55plnWLNmDe7u7sXu67HHHrP+3bJlSyIiIujduzenT58mJibmluKsSE71bq1fvz7BwcGcOnWK3r17Ex4eTmpqqk0Zo9FIWlpaide74cbNr/ZkMBgAS8ufVqut8OeryaSuK4/UdeWRuq5cUt+Vx551ff0AhRvZu3cvqamptGvXznqfyWRi06ZNfP755+h0OjQajc02nTt3BuDUqVPExMQQHh7Orl27bMqkpKQA3DDvqGhONc9cYmIiV65cISIiAoDY2FjS09PZu3evtcy6deswm83W/wAhhBBCiN69e3Po0CH2799v/enQoQMPPvgg+/fvL5LIAezfvx/AJu84dOiQTUPSmjVr8PX1pVmzZpXyOorj0Ja57OxsTp06Zb0dHx/P/v37CQwMJDAwkOnTpzNs2DDCw8M5ffo0L774Ig0aNKB///4ANG3alAEDBjB+/Hhmz56NwWBg4sSJ3H///TKSVQghhBBWPj4+tGjRwuY+Ly8vgoKCaNGiBadPn2bhwoUMGjSIoKAgDh48yLPPPkuPHj2sU5j069ePZs2a8fDDD/Pee++RnJzMq6++yoQJEyrlil9JHNoyt2fPHtq2bWsd/TF58mTatm3L66+/jkaj4eDBg9x99900atSIcePG0b59ezZv3mxTYT/88ANNmjShd+/eDBo0iO7du/O///3PUS9JCCGEEE7I1dWVv/76i379+tGkSROee+45hg0bxm+//WYto9Fo+P3339FoNMTGxvLQQw/xyCOP2MxL5wgObZnr2bMniqKU+PiqVatuuo/AwMBKmyDYZDJZr/OXhsFgwMXFhfz8fEwmUwVG5jy0Wm2xTdlCOCOjyczF9HwSr+aSmW8kz2BErVLhodXg5eZCmK87UQEeuGvlPV9TlPU8UVOV9vxY0eeMDRs2WP+uXbs2GzduvOk2devWZcWKFWV6nl9//bWsodG3b188PDxKVdapBkA4iqIoJCcnk56eXubtwsPDOX/+vMxZdB1/f3/Cw8OlToTT0RlNbDpxma2nLrPnXBpxSVkYzSV/IS0Q5utG80g/Wtbyo20dfzpFB+LpKoff6qS854maqiznx+pwzrj33nvLVF6lUnHy5Enq169fqvKlOpqUtNTWjcyePbvIHHDOquADGhoaiqenZ6nfUGazmezsbLy9vW2GTNdUiqKQm5tr7Tha0KFUlN09n2/h4IWMYh8L8nJlz6t9rbdHfLWdXWfTii3rodVw9M3C9Y7HzNvFhhOXSnze+Bl3Wv9++scDrDyaUmLZo9MH4OFq+Ub93E8HWPp3Yoll97zShyBvS/eJ1345zPc7z5VYdvOLvYgKsIxem7HiGP/bfKbEsqsn9aBhmA8AH605wafrTpZY9pcnu9G6tj8Aszee5t2VcYUPKlBSyqZRqzDdJKFLydSRkpnKurjUIo+prP/A5yPbcWcry+fi94MXeWrR36BomLRjdZHt3r+vNfe1jwJgXVwK4xbsKfH537y7OQ/H1gNg2+nLPPjNzhLLvjSgCf++3TIFw/7z6Qz5YmuJZZ/p3ZBJfRoBcCIli/4fbyqx7GM96jN1YFMAzqfl0uP99SWWfbhLXd68x9K36XK2jo7v/FVsuWYRvix7shuuLo4/vpb3PFFTleb8WN3OGcXNjVsSHx+fMu27VMncL7/8wvDhw0vd3Ldw4UKys7OrRTJnMpmsH9CClSdKy2w2o9frcXd3l2TumoL3UGpqKqGhoXLJtZRSMvNJvJpHkJcr9YK9UICSeij88357lS2ybZnKKhVUtmLi5SZl+zULY3DrSNrU9mftsRSm/Xa0xLKf3t+GWgEeHErM4NcDF9mXkF4kroJMMSNPX0wMqpIzyVLGWyXK/vO9doOyRd6XJZQ9cjGT81dziQnxLlV8FeVWzhM1VWnPj9XlnDFq1KhS51AADz30EL6+vqUur1Ju1GntGrVaXeaM8sCBA6VuHqzK8vPziY+Pp169emX6jwDLmzUzMxNfX19J5q6Tl5fH2bNniY6OLnHixrIyGAysWLGCQYMGVcv5of636TT/WRHH0La1mDmiDVdz9CVe3lOpINi7cJBQeq4eg6nkj3mIT2HZjFwDepP5hmUL6rp7r76YVSUfWIO9Xa2tE5n5BnSGkvcb5OVKtt7IkQuZxCVncuFqHldy9RiNZoxmBY1aha+7Fl93F+qHehMT4k39YC+0Lmry9CX3twnw1OKisXz2cnRGcm9Q1t9Ti/Za2YQruby3Ko7fDyYBoFZBn6Zh3Nc+itZR/gR4uVpbg/L0JrJ1xhL36+vhgpuLpZ7yDSYy8wwkpOWy48wVNp64xJ6zV615jloF/ZuH81CXurSv48/VnHz++mstffr0xsXF9n3t4+5i7YunM5rIzCs5Bm83F2srqd5oJiOv5D5dXm4a6yVgg8lMem7JZT1dLX0DwdJ/8OoNynq4avC+VtZkVkjL0ZdY1l2rxsfd8nrNZoUrxZT9ZvMZ9CYzT/ZsYPMevhXlPY7cynmipirL+bEizhnVTala5tavX09gYGCpd/rnn39Sq1atcgdVFUmTuf1IXZad9SvXtaoL8Cr9aiX+nqUv6+dZ+hOYr4e21Cc8X3ct/OMYbDIr7Dmbxl/HUlh//BKnUrNL/dwFavl70LaOP23rBNA1Jogm4T4lvr+83FysiceNrItL4fmfD1qTjWHtopjUpyG1A4ufmNTDVWNNlG7GXavBXash1NedDvUCmXhHQ1Kz8vnzUDLL919gX0I6fx5O5s/DydQP9uL+jlH4ayzJ+Y3q2s1FQ4hP6WJwdVGXOvnRakpf1qUMZTVqVanLqksoO3VQ01JtX5nk2FYxakK9KorCypUrmTNnDkuWLCnz9qVK5m6//fZS7SwtLY3AwEC6d+9e5kCEECUraIRTV4OD2tUcPT/uPs/CXec4n5Zn81gtfw8ah/sQ4edOsLcbri5qtBoVBpNCZp6Bq7l6Eq/mcfZyDhcz8rmQnseF9DxrC1q4rzs9G4fQp2kYPRqFlKkvldFk5j8r4pi7NR6AJuE+zBjakrZ1Auz34osR6uPOqK71GNW1HnHJmfywI4Flf1/gzOUc/vPncTw1Gs55nmJs9/rWfoVCiOohPj6euXPnMn/+fC5dukSfPn3KtR+7DKdavXo133zzDb/99ht5eXk330AIUSbma01zaifO5bJ1RuZsjufrzWeslyV93F3o2zSMPs3C6FI/iMAytDhm5Rs4mJjB3wlX2XPuKjvOXCE5M58fd5/nx93n8fPQMqhlOINbR9IlOgj1DSovR2dk4sJ9rD9uGfwxtls0Lw5oXOlTijQJ9+Wte1vw0sAm/LL/Al9vOsPZK7nM2nCGOVvPMqJDbf59ewyR/nIp71KWDpNZIcjb1Xp5XFSunj170qZNGz7++GNHh+JUdDodS5YsYc6cOWzZsgWTycQHH3zAuHHjytRP7nrlTubOnTvH3LlzWbBgAVevXmXgwIF8++235d2dqACjR48mPT2dX375xdGhCDtR4ZzZ3Kojybz6y2EuZekAaBrhy5hu9RjcKrLUlyj/ycddS7cGwXRrEAxY+qPtjE9jfVwqKw4lkZqlY9Gu8yzadZ46gZ480LkO/2ofVaR1Ky1Hz6i5uzh0IQM3FzWf3N+GAS0cO2rOy82FBzvXZVibCN79YSW7swM4fDGTBdvPsWjXeR7oXIcne8UQ6lNz+w8N/GQzl7N1rHj6NppFlu8EKCrmPLFo0SIeeughHn/8cWbNmmXz2IYNG+jVq5f1dmhoKN27d+f999+vFv3sb2Tv3r3MmTOHRYsW0aBBAx5++GEWLVpEVFQU/fv3L3ciB2VM5vR6PUuXLuWbb75h69at9OnTh8TERP7++29atmxZ7iCEEDdmvnad1dnG0WTkGXj1l8P8duAiAHWDPHm+X2PubBlxw5ay8nDXari9UQi3NwrhtbuasfPMFX49cJE/DiWRkJbLf/+MY+bqEwxoEc6orvVoXzeAjDwDD8/ZyZGLmQR4avlmVEfa163Yy6ploVGraBOkMPWhzuxOyOTTtSfZGZ/G/G1n+XF3AqO61uPxHjFl6kNZXRS8fZTSDPUVlWrOnDm8+OKLfPXVV3z44YfFDlo4duwYYJmu4/HHH2fw4MEcPHjQaUerlkbnzp156qmn2LFjB40bN7brvkt9anjqqaeIjIzkk08+YciQISQmJvLbb7+hUqmqdeXXBEuWLKFly5Z4eHgQFBREnz59yMnJYdOmTWi1WpKTk23KT5o0idtuuw2A+fPn4+/vz6pVq2jatCne3t4MGDCApKQkR7yUass6/sGJ+sydTMni3llb+e3ARdQqeKJnDKsm9WBw60i7J3L/pFGr6NogmP8Oa8Wul/vw3rBWtI7yQ28y8+uBiwz7chtDvthqTeSCvFz5+fHYKpXIXU+lUtGtQTA/PtaFHx7tTJva/uQbzHy18Qy3vbeej/86Qc4NRtRWRwX9R8sydYoov5ycHB555BG8vb2JiIjgww8/LLZcfHw827Zt46WXXqJRo0YsXbq02HKhoaGEh4fTo0cPXn/9dY4ePWqzVnt11Lt3b+bMmcObb77JypUrb7gCVlmVumXuyy+/ZMqUKbz00ktlnsyuOlEUhTxD6ZbmMpvN5OlNuOiNdpmaxEOrsfvJPCkpiZEjR/Lee+8xZMgQsrKy2Lx5M4qi0KNHD+rXr893333HCy+8AFiG7v/www+899571n3k5ubywQcf8N1336FWq3nooYd4/vnn+eGHH+waa03WpX4Qz/VtRPNat3Y5yWRWyNEbydEZMRgV3LRq3FzUeLu5WKfwsIf1cak8tehvsnVGavl7MOvBdrS5NiFvZfNw1TC8Y22Gd6zNocQMvt1+luX7L/L3tfne/Dy0fP9oZxqEVv3jWkFS1zUmiHVxqXyw+gTHkjL5+K+T/LAzgef6NuJfHWqjcebOlaVUcCis6slcrr7kJFutUtn0y7zVshW5qsgLL7zAxo0bWb58OaGhobz88svs27ePNm3a2JSbN28ed955J35+fjz00EPMmTOHBx544Ib7LpjORa8vebqa6mDVqlWcP3+eefPm8cQTT5CXl8eIESOAW/+iXur/+e+++465c+cSERHBnXfeycMPP8zAgQNv6cmdUZ7BRLPXb75mbEU4+mZ/u39Yk5KSMBqNDB06lLp16wLYXDIfN24c8+bNsyZzv/32G/n5+QwfPtxaxmAwMHv2bGJiLLPGT5w40eGLDlc3naID6RRd+umBAC6k57E7Po29565y+lI2567kcjEjr9iTn1oFYb7uhPu5UzvAkyYRPjSN8KV5pG+Z+2WtOJTE04v+xmhW6FI/kFkPtKsyozBbRvnx/r9a88KAxny3/Rz7Eq4yZUATmkY4V58rlUpF76Zh9GocyorDSby38jgJabm8tPQQ87ae5eU7m3J7oxBHh1mhClrmzFU8m7vR+aJX4xDmjelkvd3+rb9KbCzoHB3I4n/HWm93f3d9kbn6zv73zn9uZhfZ2dnMmTOH77//nt69ewOwYMECoqKibMqZzWbmz5/PZ599BsD999/Pc889R3x8PNHR0cXuOykpiQ8++IBatWrZ/dJjVVS7dm1ef/11Xn/9ddasWcO8efNwcXHhnnvu4b777uO+++6jXbt2Zd5vqTODkSNHMnLkSOLj45k/fz4TJkwgNzcXs9nM0aNHadasWZmfXDhe69at6d27Ny1btqR///7069eP++67j4AAy+Wm0aNH8+qrr7Jjxw66dOnC/PnzGT58OF5eXtZ9eHp6WhM5sCy5UrD8iqhcCVdy+WX/BVYcSiIuOavEci5qFVqNGp3RhFmxTH2SlJFPUkY+fyek8+uBwrLRwV50qR9Il/pBxMYEEeBecreKZX8n8txPBzArcE+bSD74V+sqOdIw1Med5/o5/4lDrVZxV6tI+jYL47vt5/hs3SmOp2Qxau4uejQK4ZVBTWkcXvVbHMvD2jLn2DBqhNOnT6PX6+ncubP1vsDAwCLJ15o1a8jJyWHQoEEABAcH07dvX+bOnctbb71lU7ZOnTrW5bpat27N//3f/+HqWrP6fvbt25e+ffty9epVvv/+e+bOncu7776LyVS6q3/XK3MzT3R0NNOnT2fatGmsXr2aOXPm8NBDDzFp0iSGDh3Kp59+WuYgnIllLcv+pSprNpvJyszCx9fHbpdZ7U2j0bBmzRq2bdvG6tWr+eyzz3jllVfYuXMn0dHRhIaGMnjwYObNm0d0dDR//vknGzZssNnHPyczValUdu0LICA1M5/L2XqCvF0J8y3aUrbjzBW+2RzP2rgUa8ubRq2iRaQvHeoF0jTCl3pBntQJ9MTXQ4ubi9rarG8wmbmao+diRj5J6XnEX8nhWFIWx5IyOXMpm/jLOcRfzmHRrvOoVNAmyo/aKhVNL+fQKMLfGsO6uBRrIje8QxQzhraqEZf7qgI3Fw2P3laf+9pH8dm6U3y7/SybTlxiy8lLjOhYm+f6NbZZFaQ6KEjmqnrL3I3OF/+cN3LvayXPMfbPslum9CqhpOPMmTOHtLQ0m1UwzGYzBw8eZPr06TbnwY0bN6JWq6lfvz5+fn6OCLfKCAgI4KmnnuKpp55i37595dpHua/ZqVQq+vfvT//+/bly5Qrfffcd8+bNK+/unIZKpSr1pU6z2YzR1bI0TlVezkulUtGtWze6devG66+/Tt26dVm2bBmTJ08G4NFHH2XkyJFERUURExNDt27dHBxxzTN/21m+2HCa0V3rMe3u5tb7jyVl8t8/49h44pL1vtsaBnN3a0trTWlWf9Bq1IT6uhPq616kX1tmvoE9Z9PYcSaNbacvc/hCJn+fz+BvNPz6yVYahXlzT5taNA7z4alFf2NWLCsm/Hdoqwof5CCK8vd05bW7mvFwl7q8uzKOPw8ns2jXeX4/mMSkPo14JLZulWwpLY+7WkVyNUdPUBUfyVuWrjEVVfZWxcTEoNVq2blzJ3Xq1AHg6tWrnDhxwrqowJUrV1i+fDk//vgjzZsXHqNMJhPdu3dn9erVDBgwwHp/dHQ0arW6xvbBv3r1KnPmzLGO6m3WrBljx44t1yVWuMVJgwtaX4KCgpg0aRKTJk26ld0JB9i5cydr166lX79+hIaGsnPnTi5dukTTpoVL5RTMf/P2229LXzgHKVy705Ig5RtMfLDqOHO2xqMooNWoGN6hNmO7R9t10XFfdy13NAnjjiZhACRn5LPy8EV+3HSEU1kaTqRk8/6q49bytzUM5r/DWkoi52D1gr348qH27D6bxrRfj3DkYiZv/X6URbsSeGNwM25r6Pz96aYMaOLoEGoMb29vxo0bxwsvvEBQUBChoaG88sorNo0U3333HUFBQQwfPrxIZ/5BgwYxZ84cm2SuJtu0aRN33303vr6+dOjQAYBPP/2UN998k99++40ePXqUeZ/l+oo2Z84cWrRogbu7O+7u7rRo0YJvvvmmzPvZtGkTgwcPJjIyEpVKVWTSQkVReP3114mIiMDDw4M+ffpw8uRJmzJpaWk8+OCD+Pr64u/vz7hx48jOLvsajzWVr68vmzZtYtCgQTRq1IhXX32VDz/80GZwi1qtZvTo0ZhMJh555BEHRltzXb8CxMHEdAZ9splvtlgSuTtbRvDX5Nt5Z0hLuyZyxQn3c+fBTrV5opmZnS/15L1hrYitH4RKBa2i/PjyofbVpuWnOuhYL5BfJ3ZnxtCWBHq5cio1m4fn7GL8t3tIuJLr6PCEE3n//fe57bbbGDx4MH369KF79+60b9/e+vjcuXMZMmRIsaMyhw0bxq+//srly5crM+Riffnll7Rq1QpfX198fX2JjY3lzz//tD6en5/PhAkTCAoKwtvbm2HDhpGSkmKzj4SEBO688048PT0JDQ3lhRdewGgs/dRAEyZMYPjw4cTHx7N06VKWLl3KmTNnuP/++5kwYUL5XphSRq+99pri5eWlvPTSS8ry5cuV5cuXKy+99JLi7e2tvPbaa2Xa14oVK5RXXnlFWbp0qQIoy5Yts3n8v//9r+Ln56f88ssvyoEDB5S7775biY6OVvLy8qxlBgwYoLRu3VrZsWOHsnnzZqVBgwbKyJEjy/qySpSXl6ccPXrU5jlLy2QyKVevXlVMJpPd4nGUsWPHKoMHD7bLvm6lTkui1+uVX375RdHr9XbbZ1Xynz+OKnWn/K48Mmen0vCVFUrdKb8rHd9eo6w9llzpsRRX1+k5ekVvdP73eVVjz/d1eo5emfbrYaX+1D+UulN+Vxq+skJ5b+UxJTvfYIdIK192vkFJz7Xv+6689V0Rx7TqriznR3vW76+//qr88ccfyokTJ5Tjx48rL7/8sqLVapXDhw8riqIojz/+uFK7dm1l7dq1yp49e5QuXbooXbt2tW5vNBqVFi1aKH369FH+/vtvZcWKFUpwcLAyderUUsfg7u6uxMXFFbk/Li5OcXd3L9frKnMyFxwcrCxcuLDI/QsXLlSCgoLKFYSiKEWSObPZrISHhyvvv/++9b709HTFzc1NWbRokaIoinL06FEFUHbv3m0t8+effyoqlUq5cOFCuWO5Xk1P5tLT05XNmzcr7u7uyurVq+2yT0nmyu6t344odaf8bv0ZN3+3kp7jmNda3eu6KqmIuj6RnKk8+PUO63up8zt/Kb/8naiYzWa7PUdl6P3hBqXulN+Vracu2W2fksxVHkclc8UJCAhQvvnmGyU9PV3RarXKzz//bH3s2LFjCqBs375dURRLI5RarVaSkwu/SH/55ZeKr6+votPpSvV8Xbt2LdJ4pSiKsmzZMqVz587leg1l7jNnMBis13iv1759+zI1M95MfHw8ycnJ9OlTOLrHz8+Pzp07s337du6//362b9+Ov7+/TTx9+vRBrVazc+dOhgwZUuy+dTodOp3OettsNgMUOyxap9NhNBrR6/VlntRPUZRyb2sviYmJdOnSpcTHd+zYUWSuoOv961//Yu/evTz55JP06NHDpt7KS6/XYzQaycvLs9b9rSp47+Xl5WEwGOyyz6rCZFbYeNzSzK9RKUy4vR7/vi0aFQZycyv/tVbnuq5qKqKua/lo+GpkCzacuMKHf53kQnoeL/78N4u2n+HF/o1o4ixTmVzrepCfryM31z6XjMtb3zqdDrPZjMlkKte0Eo6WkJBwwyU5Dx06ZB34YG+Koty0zkwmk2US/hLOGTqdDpVKZdOHz83NDTe3G4/gNplM/Pzzz+Tk5BAbG8vevXsxGAw2eUeTJk2oU6cO27dvp0uXLmzfvp2WLVsSFhZmLdO/f3+eeOIJjhw5Qtu2bW/6mp9++mmeeeYZTp06ZT0/79ixg1mzZvHf//6XgwcPWsu2atXqpvuDcgyAePjhh/nyyy+ZOXOmzf3/+9//ePDBB8u6uxIVLCF1fYUV3C54LDk5mdDQUJvHXVxcCAwMLLIE1fVmzJjB9OnTrbc9PDxYtGjRDeM5c+ZMmeKvSm7Un/Hw4cMcPny4xMevv36/cuVKu8ZVEXW6fv16u++zKoh0UXMSNb0iFWJ0p/nrr9OODqna1nVVVFF1/Uyj62+lcf7QDs4fqpCnsrucHA2gYs+evWSdsu/0JGWtbxcXF8LDw8nJyXHKLzg+Pj5s2rTpho9nZZU8b+WtKM1+9Xo9+fn5bNu2rdhGo0WLFrF48WKb+9544w2mTZtW7P4OHTpEbGws+fn5eHt7s2zZMpo1a8b+/ftxdXXF39/fpvw/847i8pKCx0pj5MiRALz44ovFPlYwvZdKpSr1l4NyjWadM2cOq1evtmaUO3fuJCEhgUceecQ6nQVQJOGrKqZOnWoT581a5hITE6lbt+5Ns/x/UhSF7OxsvL29nWpNzYqm0+k4d+4cUVFRZa7TkhiNRtavX0+vXr1wcam8IfsVSVEUpv0ex68Hk1Chom/TYP7VNpLuDYIcGld1rOuqqrLqOiVTxyfrTvHnEUsLsLerC4/1qMf9HWqj1VTNY9cXZ3aRlJtDu3bt6BpTttVRSlLe+tbpdFy8eBEvL69iF5V3BgUTxVemrKysUk1Nkp+fj7u7O127di32nNGjRw9mz55dpGWuJI0bN2b//v1kZGSwZMkSRo0axcaNG8v3IsohPj7e7vss89Hh8OHD1nlQTp+2tA4EBwcTHBxs08Jzq8lLeHg4ACkpKURERFjvT0lJsa4FFx4eXmSlAaPRSFpamnX74pSm+bWAWq3GxcUFV1fXMiceZrPZum1VnmeusimKgouLCx4eHnY78BV8G/bw8CgyibGz+mDVcX7+Oxm1SsVXD3egb7Owm29UCapjXVdVlVXX0Z6efPxARx46m8b0345y6EIGM1ad5se9ybx6Z1PuaBJa5b6QatSWSdS1bm54enraZZ/lrW+1Wo1arUaj0aDR2H9y9+qooBHln5dHi6PRaFCr1SWeM8r6/+/q6kqDBg0ASxex3bt388knnzBixAj0ej3p6ek2rXMpKSnWnCI8PJxdu3bZ7K9gtOuN8o7rFSydaU9lTuYq69JKdHQ04eHhrF271pq8ZWZmsnPnTp544gkAYmNjSU9PZ+/evdYh0uvWrcNsNtssO2IP9urbJaQuS2PRrgQ+X38KgP8MaVllEjlRvXWoF8jyCd1Ysi+R91YeJ/5yDuMW7KFHoxBev6spDUKrTn+6gvO/UoVWgJBjW8Wo6Ho1m83odDrat2+PVqtl7dq1DBs2DIDjx4+TkJBAbKxlXdzY2FjeeecdUlNTrd281qxZg6+v7w2XNf31118ZOHBgqb8krFixgl69etmspnEjDr1Gkp2dzalTp6y34+Pj2b9/P4GBgdSpU4dJkybx9ttv07BhQ6Kjo3nttdeIjIzk3nvvBaBp06YMGDCA8ePHM3v2bAwGAxMnTuT+++8nMjLSLjEWtKpdvHiRkJAQXF1dS/0N1Ww2W6/1S8uc5aCr1+u5dOkSarW6Wq3DZzIrXEzP49yVXK7k6EjL0ZOrN6EoCmbFMj+cr4cWH3cX/D1cifT3ICrAAy+3oh/BA+fTeWP5EQAm9WnI/Z3qcDlbR3a+EX9PbalWdRCivNRqywTUA1uE8/n6U8zdEs+mE5fo//FlHu5Sl2f7NMLP0/Etsiosx+GqkMvdynmipirN+bEizhlTp05l4MCB1KlTh6ysLBYuXMiGDRtYtWoVfn5+jBs3jsmTJxMYGIivry9PPfUUsbGx1m5l/fr1o1mzZjz88MO89957JCcn8+qrrzJhwoQbXr0bMmQIycnJhISUbsLu+++/n/3791O/fv1SlS9VMjd06FDmz5+Pr69vqXb64IMP8tFHHxUZnPBPe/bsoVevwvXlCvqxjRo1ivnz5/Piiy+Sk5PDY489Rnp6Ot27d2flypU2zaw//PADEydOpHfv3qjVaoYNG2bX9WHVajXR0dEkJSVx8eLFMm2rKAp5eXl4eHjIB/s6np6e1KlTx6kT3EtZOnacucKu+DT2nrvK6UvZ6Ixl//YY6OVK3SBPmkX40izSl/rB3jz/8wH0JjP9moXxTO+GAMxcc4KFOxOY1Kchk/o0uslehbh1Pu5apg5sysiOdXhnxTHWHE1h/razLN9/gcl9GzGyUx1cHDhB9O2NQogO9iLEx/Frzt7KeaKmKsv50Z7njNTUVB555BGSkpLw8/OjVatWrFq1ir59+wLw0UcfWXMJnU5H//79+eKLL6zbazQafv/9d5544gliY2Px8vJi1KhRN10dSVEURo8eXeruWvn5+WV6XSqlFG3UGo2GEydOlCqjVBSF2rVrlymjdAYF04yUZdi5wWBg06ZN9OjRQ/oWXaPRaHBxcbF7cmswGFixYgWDBg2qsLq+mqPnj0NJ/H7wIjvj04q0CLhq1NQJ8iTY25UgLze83DRo1KprI5IUsnQGMvOMpOXouZCeR0ZeyaPe6gV58utT3fF1t7yWqUsPsWhXApP7NuLpawmeo1RGXQuLqlTXW05e5s3fj3AixbLCTuMwH14f3IxuDYIdGpc93Wp9l+c8UVOV9vxYUeeMyjZmzJgyb/P+++8THFy6z1epWuYURaFRo5rdGqBSqdBqtWX6gGs0GoxGI+7u7g4/EIvyO5mSxdyt8Szdd8Gm9a1phC+dowPpFB1I80hfogI80ZRhTdLMfAOJaXmcupTN0YuZHE3K5FhSJoqi8OVD7a2JHBT2C5IlT4WjdG8YzIqnb2PhrgQ+XH2C4ylZPPjNTvo1C+PlQU2pF+zl6BAdrjzniZqqpp0f582bV6H7L1UyV55BD7Vq1SrzNkJUJefTcpm55gS/7L9gbYVrFuHLPW0iubNVBFEBtzaCztddS7NILc0ifbm7dWEfz4L5ha5X8PzO/u1UODcXjZpHYusxuFUkH/91gu93JrD6aArr4lJ5qEtdnrqjAUHelXPZ02gyY1bARa1CLd9yRA1XqmTu9ttvr+g4hKgy8g0mvlh/ii83nsZgsmRR/ZuH8eht9elQN6DCE6ri9m++ls1JLieqggAvV6bf04IHu9TlnT+OsfHEJeZvO8uSvYk80TOGsd2i8XCt2Ck67v/fDvacu8qXD7ZjYMuIm28gRDUmM34KcZ29567ywpIDnLmUA8BtDYN5sX8TWkb5OTQu87WWObVkc6IKaRTmw4Kxndh66jIz/jzG4QuZvL/qON9tP8fkfo0Y1i6qTF0PyqLgs1AFBrMK4XCSzAmB5dLmN5vjeXdlHEazQoiPG2/e3ZwBLcKrxKVNBekzJ6qubg2C+XVCd347eJH3Vh63rPe65CBzNsfz0qAm9GwUYvfPUcHuzFVhbhIhHEySOVHj5eqNPLt4P6uuLWV0V6sI3rm3ZZWYS6vAbQ2D8XXX0jzSsS2EQpRErVZxT5ta9G8eznfbz/HZupMcT8lizLzddI0J4vn+jWlXx35LRhUkc5LLCWeiKAqnTp1Cr9fTuHFjuy3T57wTfQlhB5ezdYz8eierjqTgqlHz1r0t+Gxk2yqVyAEMaRvFtLubV6tpIET15K7VML5HfTa92IvHetTHVaNm2+krDP1iG+Pm7+bwhQy7PE/BZVZpmRPOIj4+nlatWtGkSRNatWpFTEwMe/bsscu+JZkTNVbi1VyGfbmNA+fT8ffUsuixzjzcpW6VuKwqhLPz93Tl5UFNWff87QzvYOk7tzYulbs+28IT3+/lRErWLe1f+o8KZ/PCCy9gNBr5/vvvWbJkCVFRUfz73/+2y77L3L6XkpLC888/z9q1a0lNTS2yLp5MliicwcX0PEZ+vYPzaXlEBXiwYGwnYkK8HR1WidJz9eiNZrzdXfB0ld4RwnlEBXjy3n2teaJnAz756wTLD1zkz8PJrDySzD2tI3mmTyOiyzFHnfSZE85my5YtLFmyhO7duwPQpUsXoqKiyMnJwcvr1uZpLPNZYfTo0SQkJPDaa68REREhrRjC6aRk5vPAtUSubpAnix+LJdzP/eYbOtAryw7zx6Ekpg1uxuhu0Y4OR4gyiw724uP72/JkrwZ8tOYEfx5O5pf9F/ntYBJD29biyV4NypTUtasTgLtWQ7hv6RYiF8LRUlNTadiwcAWfiIgIPDw8SE1NJTr61o7rZU7mtmzZwubNm2nTps0tPbEQjpCjMzJm3m7OXsklKsCDheO7VPlEDq4bzSrDWYWTaxTmw5cPtefwhQxmrjnBurhUft6byP/tS+SuVpFM6NWAxuE+N93Ps31r9qpEwvmoVCqys7Px8Cj8AqJWq8nKyiIzM9N6n6+vb5n3XeZkrnbt2kUurQrhDExmhacX/c3RpEyCvV1ZNL4Ltfyd41u9+doqYtISLqqLFrX8mDu6I/sSrjJr3SnWxqXy64GL/HrgIn2bhTGxVwNa1/Z3dJhC2E1xS6MqikLbtm2tf1vW8i57d7UyJ3Mff/wxL730El999RX16tUr8xMK4Sj/WXGMtXGpuLmo+fqRDtQOvLXluCpTQcucpHKiumlXJ4A5ozty5GIGX6w/zYrDSaw5msKaoync1jCYCb0a0Dk6UL7ICKdXnqVRS6vMydyIESPIzc0lJiYGT0/PIgvkpqWl2S04IexlxaEk5myJB2Dm8Da0teN8V5VBVoAQ1V3zSD9mPdiOU6nZfLHhFMv3X2TzyctsPnmZ1lF+PHpbfQa2CMdFY5mE4fHv9rI2LoW3723BiI51HBy9EDdXmqVRy5tDlTmZ++ijj+QbknAq567kMGXJQQAevz2GO1s53zqOijWZc2wcQlS0BqHezBzehmf7NGL2xtP8vDeRA4kZPLXob2r5ezCmWz1GdKyNSVEwmBRMZkdHLMStW716Nd988w2//fYbeXl5Zd6+XKNZhXAWeqOZCQv3kaUz0qFuAM/1c85O0wX9VOV7lKgpagd68s6QljzbtxHfbT/HdzvOcSE9j7f/OMYnf50kyNsVkKlJhPM6d+4cc+fOZcGCBVy9epWBAwfy7bfflmtfZZ40+Pbbb+fbb78tV+ZYHtOmTUOlUtn8NGnSxPp4fn4+EyZMICgoCG9vb4YNG0ZKSkqlxCaqvs/XneTwhUwCPLV89kBbtBrnnCe7e8NghneIon4VngtPiIoQ7O3Gs30bse2lO5gxtCUxIV5k6YycvZILwMKdCWw7dVkG5olS2bRpE4MHDyYyMhKVSsUvv/xi8/jo0aOL5BwDBgywKZOWlsaDDz6Ir68v/v7+jBs3juzs7FI9v16v58cff6RPnz40adKEffv2kZiYyJYtW/jxxx/517/+Va7XVeYzW9u2bXn++ecJDw9n/Pjx7Nixo1xPXBbNmzcnKSnJ+rNlyxbrY88++yy//fYbP//8Mxs3buTixYsMHTq0wmMSVd/hCxnM2nAagLfvbUmEn3OMXC3OmG7RvHdfazrWC3R0KEI4hLtWw8hOdVjz7O3MG92RIC9Ly9zRpEwe+GYnfWZuZN7WeDLyDA6OVFRlOTk5tG7dmlmzZpVYZsCAATY5x6JFi2wef/DBBzly5Ahr1qzh999/Z9OmTTz22GM3fe6nnnqKyMhIPvnkE4YMGUJiYiK//fYbKpUKjUZzS6+rXKNZP/jgA3799VcWLFhAjx49aNCgAWPHjuXhhx8mLCzslgIqNkgXF8LDw4vcn5GRwZw5c1i4cCF33HEHAPPmzaNp06bs2LGDLl262D0WUbWYzAppOXquZOVxNgs2nriEUVFhNCt8vu4UJrPCoJbhTtlPTghRlFqtoleTULrEBPHHwSQ61gvg6MVMTl/KYfpvR3lv5XHubRvJg53r0qKWn6PDFVXMwIEDGThw4A3LuLm5FZtzABw7doyVK1eye/duOnToAMBnn33GoEGD+OCDD4iMjCxxv19++SVTpkzhpZdewsfn5nMplkW51gVycXFh6NChDB06lNTUVP73v//x2muv8fLLLzNo0CCefvppa3JlDydPniQyMhJ3d3diY2OZMWMGderUYe/evRgMBvr06WMt26RJE+rUqcP27dtLTOZ0Oh06nc5623xtEi9XV1e7xQxgNBoByMvLw2CQb4u3It9g5kRqFnHJ2ZxMzeb81VyS0nUkZeRhKBjqiQaO7bPZLtxby8v9YsjNza38oO0oR29EUcDNRe3wS8Xyvq48UtclM1+bi6tP42C+uL8lfxxK5qc9iZy+nMPSvQks3ZtAiwhf7m4dwYDmYfi43/x0J/VdeexZ1zqdDpVKhVpdeGx0c3PDzc2tXPvbsGEDoaGhBAQEcMcdd/D2228TFBQEwPbt2/H397cmcgB9+vRBrVazc+dOhgwZUuJ+v/vuO+bOnUtERAR33nknDz/88E0Ty9K6pUUed+3axbx58/jxxx8JDQ1l9OjRXLhwgbvuuosnn3ySDz744JYD7Ny5M/Pnz6dx48YkJSUxffp0brvtNg4fPkxycjKurq74+/vbbBMWFkZycnKJ+5wxYwbTp0+33vbw8CjSjGpPFTm3TE0TAHRygU4hQEhptjCxd9vGig2qEsw6quZEhpqHG5joEFI1+gbJ+7rySF0Xpc5U0chPRUp8HNsyjxEA/Ls+UP/6Ulch7So7Nh8t076lviuPPep60aJFLF682Oa+N954g2nTppV5XwMGDGDo0KFER0dz+vRpXn75ZQYOHMj27dvRaDQkJycTGhpqs42LiwuBgYE3zDsARo4cyciRI4mPj2f+/PlMmDCB3NxczGYzR48epVmzZmWOt4BKKWOv0dTUVL777jvmzZvHyZMnGTx4MI8++ij9+/e3TlmyZcsWBgwYUOoOgWWRnp5O3bp1mTlzJh4eHowZM8amlQ2gU6dO9OrVi3fffbfYfVRmy9z69evp1asXLi6yOPrNmMwKe85dZdXRVNYeSyVTZ7R5PMjLlabhPjQO86ZOoCeR/u5E+XsQ4uOGYjZV67oe8+3f7DybzvtDm3FnC/t3ZSgLeV9XHqnr8kvL0bPicDLL9ydz6nLhuSjc1527WoZxd6tIagfa9qOV+q489qzr8rbMqVQqli1bxr333ltimTNnzhATE8Nff/1F7969+c9//sOCBQs4fvy4TbnQ0FCmT5/OE088Ueq4FUVh9erVzJkzh19//ZXg4GCGDh3Kp59+Wup9FChzDUZFRRETE8PYsWMZPXo0ISFFm0datWpFx44dyxxMafj7+9OoUSNOnTpF37590ev1pKen27TOpaSklHi9G26t+bUsCpqOPTw8ikyuLApdztbx464Evt+RQHJmvvX+YG93bmsYTNeYILo2CL7h0lvVva5V1w5S7m5ueHo6duWK6l7XVYnUdfl5enryWC9/xvdszKELGfy8J5Hl+y9w7qqOWZsSmLUpgdZRfgxuHcldrSIJ93OX+q5E9qzrijwm1q9fn+DgYE6dOkXv3r0JDw8nNTXVpozRaCQtLe2GeUdxVCoV/fv3p3///qSlpfHtt98yb968csVZ5mRu7dq13HbbbTcs4+vrW2HN1NnZ2Zw+fZqHH36Y9u3bo9VqWbt2LcOGDQPg+PHjJCQkEBsbWyHPL+znYnoen68/xZI9ieivzfwZ4KllUMsIBreOpGO9QDQySy5QOGmwzDMnRNmoVCpaRfnTKsqfV+5sypqjKfy8N5EtJy9xIDGDA4kZvLPiGJ3qBXJnyzA00lVOXCcxMZErV64QEWEZRBcbG0t6ejp79+6lffv2AKxbtw6z2Uznzp3L/TyBgYFMmjSJSZMmlWv7MidzHTp0IDc315oJnzt3jmXLltGsWTP69etXriBu5Pnnn2fw4MHUrVuXixcv8sYbb6DRaBg5ciR+fn6MGzeOyZMnExgYiK+vL0899RSxsbEykrUKS8vR8+nakyzcmWBN4lrX9mdM13oMahmBq4tzzgVXkRRZzksIG68vP8yyvy8wqU8jxnWPLtU27loNg1tHMrh1JJeydKw4lMRvBy6y59xVdsansTM+DbVKw+qMfQxqFUHvpmEEe1f8VRxRebKzszl16pT1dnx8PPv37ycwMJDAwECmT5/OsGHDCA8P5/Tp07z44os0aNCA/v37A9C0aVMGDBjA+PHjmT17NgaDgYkTJ3L//fffcCTr5MmTSxWfSqXiww8/LPPrKnMyd8899zB06FAef/xx0tPT6dy5M1qtlsuXLzNz5swyXS8ujcTEREaOHMmVK1cICQmhe/fu7Nixw3p596OPPkKtVjNs2DB0Oh39+/fniy++sGsMwj7MZoXFe87z7so40nMtX387RwcyuW8jOtcPcnB0VZuCJZuThkohLPL0JrLyjeiMpnJtH+Ljxqiu9RjVtR4X0vP4/cBFlu+/wNGkLDaevMzGk5dRqw7RoW4g/ZqH0b95OLUDHdvFQdy6PXv20KtXL+vtgiRr1KhRfPnllxw8eJAFCxaQnp5OZGQk/fr146233rLpmvXDDz8wceJEevfubc0/btbP7e+//7a5vW/fPoxGI40bNwbgxIkTaDQaa2tfWZU5mdu3bx8fffQRAEuWLCEsLIy///6b//u//+P111+3ezL3448/3vBxd3d3Zs2adcMJAIXjxV/O4bmf9rMvIR2AJuE+vHpnM7o1CJK1fkvBOvsKUldCQGErtT0Wfqjl78G/b49hbNc6zPu/FeQGNWFt3CUOXchg19k0dp1N4+0/jtEk3If+zcPp0zSM5pG+qOXbldPp2bPnDVcLWbVq1U33ERgYyMKFC8v0vNd3PZs5cyY+Pj4sWLCAgIAAAK5evcqYMWNu2o2tJGVO5nJzc62T3a1evZqhQ4eiVqvp0qUL586dK1cQovpSFIWf9pxn2q9HyTOY8HLVMLlfY0bF1sXFSZfWcoTuDYKJ9Pcgws/d0aEIUSUUDFy09zJeYR4wqGd9JvVtzIX0PNYcSWbVkRR2nU0jLjmLuOQsPll7kmBvV3o0DOH2xiHc1jCEQC/7zoYgqq8PP/yQ1atXWxM5gICAAN5++2369evHc889V+Z9ljmZa9CgAb/88gtDhgxh1apVPPvss4BlyhJfX98yByCqr1y9kReWHOSPg0kAdKkfyMzhbYi8wahUUbxn+zZydAhCVDGWVjFzBU67WMvfg9HdohndLZqrOXrWxqWy+kgyW09d5nK2nqV/X2Dp3xdQqaB1lD89G4dwe6MQWkX5y+AtUaLMzEwuXbpU5P5Lly6RlZVVrn2WOZl7/fXXeeCBB3j22Wfp3bu3ddTo6tWradu2bbmCENXPxfQ8Hl2wh6NJmbioVTzfvzHjb6svBzghhF0UHErs3DBXogAvV+5rH8V97aPQG83sPXeVDSdS2Xj8EnHJWew/n87+8+l8/NdJfNxc6BQdSGxMELExQTQNl0uyotCQIUMYM2YMH374IZ06dQJg586dvPDCC+VeW77Mydx9991H9+7dSUpKonXr1tb7e/fubbOMRWJiIpGRkTaT+ImaYf/5dB5dsIfL2TqCvV2Z/VB7OsgC8bfEeG3Ur0atkj6GQlA4TY+5srK567i6qK2J2tSBTUnOyGfjiVQ2nrjE5pOXyco3sjYulbVxlvnI/D21dI4OJLZ+EF1igmgU6iPJXQ02e/Zsnn/+eR544AHrfHsuLi6MGzeO999/v1z7LNe0y+Hh4UUmxyvILgs0a9aM/fv3U7++zdoqoprbdvoyjy7YQ67eRNMIX75+pD1RATIC7FbdM2srRy5mMn9MR3o2Dr35BkJUc3UCPWlfN4BIf8f3Iw33c2dExzqM6FgHk1nh6MVMtp2+zPYzV9gdn0Z6roFVR1JYdSQFAB93F9rWCaB9nQDa1w2gTR1/vN1kxYmawtPTky+++IL333+f06dPAxATE4OXl5dNubI0ilXYu8fenVJF1bc+LpXHv9+LzmjmtobBzH6oPV5ygLILmWdOCFuP9YjhsR4xjg6jCI1aRcsoP1pG+fHv22MwmMwcupDB9tNX2HHmCnvOXiUr38imE5fYdMLSb0qtgsbhvrSv60+7OgG0ivKnfrCXtN5Vc15eXrRq1arEx8vSKCZnWmEXa4+l8Pj3ezGYFPo0DePzB9rirtU4Oqxqo+BSkuRyQjgXrUZNuzoBtKsTwIReDTCazMQlZ7Ev4Sp7z1l+Eq/mcSwpk2NJmXy/IwEAL1cNzSP9aFHLj5ZRvrSs5Ud0sLf0O65BytIoJsmcuGW7z6bx5A/7MJgU7mwVwccj2qCVaUcqhLTMCeHcXDRqWtSyJGmPxNYDICUzn33XErt9CVc5mpRJjt5kneOugKerhuaRvjSP9KNJuA+Nwn1oHOYjV0CEJHPi1sQlZzJu/m50RjN3NAmVRK6CWFvmHByHEFXFp2tP8sPOczzcpS4T72jo6HBuSZivOwNbRjCwpWX9T6PJzOlLORy6kMHhCxkcupDB0YuZ5OpN7D57ld1nr9psXzvQg8ZhPjQO96FxuC+Nw3yoH+Ilx+IapMKSORlxV/0lZeQxau4uMvONtK8bwKwH2snBo4IUtLbL50oIi6x8AymZOrLyjY4Oxe5cNOpriZkP97WPAsBkVjh9KZtDiRkcTcrkRIplAuNLWTrOp+VxPi2Pv46lFu5DraJOoCfRwV7UD/EiOtib6GAvYkK8CPFxk2NJNSMDIES55BtMPP7dXlIydTQM9WbuqI54uEofuYoifeaEsGVdzsvBcVQWjVpFozAfGoX5MOy6+9Ny9BxPzrImdydSsjiRnEWWzsiZyzmcuZzD2jjbfXm7uRAd7EV0sBd1gzypHeBJVKAHtQM8ifBzl9V5qoiyJNwVlswdPXqUyMjIitq9cCBFUXh52SEOJGbg76llzqiO+HlqHR1WtdY1JpjoYC9ZMkiIawpOdOaKXALCCQR6uVrnvCugKArJmfnEX7Ikc2cu5RB/OZszl3M4n5ZLts7IoWuXb/9Jo1YR4edOVIAluasd6ElUgAdR1xK9UF833Fzki3tlqJABEKWdlXjp0qUA1K5du9RBiCrGbAJ1yR/W73ecY+m+C2jUKmY90I46QVVkHjnF5OgIyucm9Q3w1r0tKimYUqrGdS2cQ+GkwY6NoypSqVRE+HkQ4edB1wbBNo/pjWYS0iwJXkFyd/5qHolpuSRezUNvMpN4NY/Eq3nsIK3Y/Qd5uRLu5064rzthfu5EFPy+7j4fNxe5lHuLytIoVupkzs/Pr9wBCSeStg/2PgPtP4HAdkUePnoxk7f+OAbA1IFN6PaPA4XDXP2b7vmvwtUoCO108/JVxU3qu0qSuhZVgHU5rxpzodU+XF3UNAj1oUGoT5HHzGaFS9m6awleLufT8ki89vtCeh7JmfnojWau5Oi5kqPnyMXMEp/HzUVNiI+b5cfb8ntkpzq0qFVzc4mKbBQrdTI3b968Uu9UOCmzEQ69CekHLb9vWwLqwrdIrt7IU4v2oTea6d0klHHdox0Y7HXMRjRH38bXfA7N0bcheKlN3FXWTeq7SpK6FlWE6trYbumebT9qtYowX3fCfN2LXYJRURSu5hpIzsgnJTOfpIx8kjPzSc7IIzlTR0pGPkkZeWTmG9EZC1v4CvRpFlaZL6fKqchGsWpzNJs1axbvv/8+ycnJtG7dms8++6zIEmPiJs4thrTd4BZi+Z3wE9R7wPrwW78f4/SlHEJ93Hj/X62rThP6ucWo0vagU/nilbanSNxV1k3q+3o931/PxfR8fn48lta1/Ss3zuvVgLoWziHU140m4T6E+ro5OpQaQ6VSEejlSqCXK80ifUssl28wcSlLR2qWjktZOi5lW343DPWuxGhL5qh8oSIbxarFkJXFixczefJk3njjDfbt20fr1q3p378/qampN99YWORfgrgPARW4+lt+H/vAcj+w5eRlFu2yzEz+8Yg2Vacj/rW4FZUKo8oLRWUbd5V1k/r+J4NJQW8yO3Y0aw2pa+EcHomtx8pJPXiyZwNHhyL+wV2rofa1tXMHtAjn4S51mdy3UZVYp7u65gvVIpmbOXMm48ePZ8yYMTRr1ozZs2fj6enJ3LlzHR2a84ibCXkXwT3Ects9xHL7+Mfk6IxM+b+DADwSW7dIh1qHKojb7VrcboVxV2k3qO/iKNZJgx2YzdWQuhZCVF/VNV9w+suser2evXv3MnXqVOt9arWaPn36sH379mK30el06HQ6622z2QyAq6t9W5sW70lg8VE1X5zZgkpVfN7coY6fda29M5dzuZStL3F/baN8cXWx7OfslVxSskou27qWj3Vt1IS0PJIydSWW7R2WzCh+RsGXjZcbsTPTMpu6BgOcMXBgxTKSMz3wdXPhvtZh5ObmArD9TBrrTlwucb8Pdoyi3rWRrnsT0ll5tORvPsPbRVqb4A8kZvD74ZQSyw5pHUGzCB9UV/dz/OhO/i91DIraFYNej9bVFZVZDwkmzEf/4q72zWkdZemncOpSDov3Xihxv/2bhtKhrj8A59Jy+X5XYollezUKpmt9S5+Sixn5zNueUGLZ7jFB3N7QMm3ApWwdX/+1F1WqAowB9XXvObOeLhnH6RWyHfxbk55nYNbGeACu5hoA0Onyyc2t/GlgVFf3ozm3DEUThKK4Y8SAXnFHpQlEdfb/MIbcBf6tKz2um7k+bhT3a5OSaaGKx13AaLRMiJuXl4fBYHBwNFXPpSwd32xLsM7DeD1XjZoX+ha22v245wKnL+cUux8VKl4e0NBa3z9sj+fk5dwSn/fFvg2sE6T/ejCZQzcYCPDsHTF4XpuD888jKew7X3Q6kAITbo/G38Py+f4r7hI7/7HSw/X+3b0ewd6W48fGE5fZcqb4kacAY2LrEOnnDsC202msP1nycfuhTlHUDbQct3efvcrquJJbsEe0r0WDEC/g5sft6/cL9n1v63Q6VCoVanXhedbNzQ03N9tL8OXJF5yF0ydzly9fxmQyERZm27EyLCyMuLi4YreZMWMG06dPt9728PBg0aJFdo8tEPBwUfN3SskHhbF10nG71thyLENN3KWSG0sfrJWO77WyZzLVxKWUXHZ4RDpB18omZquJSy657D1h7mz3/BCAdVdUrLxc/NQNmToT67dsI/7aIKh1F1UsP1fyNA8B2Qk09LMcZLckq/g5vuSynpkJtAiwlN2VquKH0yWXdbmawIVgS9n9+ZP4LrmEssk6zNm7SQ21lD16VcUPcSXvNz81gasRlrKnMuCHoyV/PDKTE8g5bSmbkA0/HCq57OWL59GftXxhSM6F7w+6ALcVWzZR1RXj7lRgDWk6+GGf7X4P7NnBBfcSn6qCvWv5ZQQ8rv0ucC3mqula3MWdL6p03IXWr1/v6BCqnD2XVKxMVHMpv/jWaje1Qhvirbd/OqomLqP446AKhY6as9bbS3cc52BaycfMtqpzaK89/H8n1ey+XHLZ5uZzeF37/vV/Z9Rsu8Fxu4HxHIHX8o9lZ9VsSCq5bF39OcI8LH+vSFCz6kLJZSPyzlH7Wne1NRdU/J5Q8nEwMOccDa51h9uUpOL/zpZc1iczgabXjts7UlUsusFxOzD7HA2K6f9vj/f2okWLWLx4sc19b7zxBtOmTbO5rzz5grNw+mSuPKZOncrkyZOttyuqZW7J7nj27v2Z9t16odEU/yaPdy1smQsKyaWDR8mtbefdfXG99m3QNziXDm4ll73o7kPatZY5z8A8OriU3DJn1ibTLe8lFDSo3FsSFmYZpapBjwqFk9reePhEEB3syV3tIom49g0vMCGdiNMlfxsc3CaC2gGWo03YhUyCbtCKN7hlONHBlm9ttZKy8Iks+dvggGahNArzhvQDNNj4Eu612qGoXLl06RIhISGoFEvc5si76dOmOc0iLNlnwyu5qEKSS9zv7Q2DrK14Ta/mYQhKKrFs1/qB1la8lEwdOf4lt/h1rOtP7LVWvLQcPVdd96K++BsKKtBc983RpKO9zxm6dpwK/q3IzDeQ4nXe+nBMiBd3tnDQaLD0A7jsHoOCBrPamyNHDtO8eQvU5ixUmDF2nA/+rRwT241cFzfa6zpsGzKqdtzXZGVl0axZM44ePYqPT9GpJGqyWJ2R8H0XS1zOS6tR07dHPevtnNAkEtLyii2rAvr2qm+t74+WbqZrtrnE5+7bo561ZU6JSqV9cnaJZft3r4vHtWOx5vhlWt2gFW9gbG183S2Zn9vJKzRJLLkVb1CnKGvfZe8zaUSfSy+x7J3taxF2baBIwLl0om7Qind32whq+VuO26GJGYScvFJy2Vbh1qsvkRez8LvBcXtwmwiirp0PwL7v7R49ejB79uwiLXM1iUpx8nW39Ho9np6eLFmyhHvvvdd6/6hRo0hPT2f58uUOiy0zMxM/Pz8yMjLw9S155E+VsP9lODMXPCJApbFMCpuXBDHjoPU7jo6uZNfiNrmG8vf+Q7Rt0xKNPtVp4naq+pa6rnROdQypBqS+K48j6roq5wu3yukHQLi6utK+fXvWrl1rvc9sNrN27VpiY2MdGJmTafIseEQWjvDLv2S53XiSQ8O6qWtxq/SWVj+V/rJTxe1U9S11LYRwYtU5X3D6ZA5g8uTJfP311yxYsIBjx47xxBNPkJOTw5gxYxwdmvNwD4EmzwEK6NMtv5s+XzgCsKoqiFtR8PPEMoOoM8XtTPUtdS2EcHLVNV+oFn3mRowYwaVLl3j99ddJTk6mTZs2rFy5skgnx8rm5ubGG2+84TzX7uuOgISfIXU9hPaCOsMdHVHp1B2BcvZHaueuQQns6FRxO119S11XKqc7hjg5qe/K46i6rqr5wq1y+j5zws6cdf1KibvyOGPM4LxxCyHETUgyJ4oym0Bd8hDzKkvirjzOGDM4b9xCCHEDkswJIYQQQjixajEAQgghhBCippJkTgghhBDCiUkyJ4QQQgjhxCSZE0IIIYRwYpLMCSGEEEI4MUnmhBBCCCGcmCRzQgghhBBOrFokc5s2bWLw4MFERkaiUqn45ZdfHB0SAIqiYDAYkKn8Kp7UdeWRuq48UteVS+q78khd21e1SOZycnJo3bo1s2bNcnQoNoxGIytWrMBoNDo6lGpP6rrySF1XHqnryiX1XXmkru3LxdEB2MPAgQMZOHCgo8MQQgghhKh01SKZKyudTodOp7PeVhQFjUaDm5ubXZ/n5WWH2HBUw8zjm1GpVHbdt7ClKAq5udW7rhuEePHZ/a1x0Ti2Qd1gMNj8FhVH6rpkJ1OyeWnZYTLz7deyUxOOI4709j3N6BwdCNj/ve3i4lKj/8+q3dqsKpWKZcuWce+995ZYZtq0aUyfPt3mvhEjRjBy5Ei7xvLVMTVH06vFlWxRRbzYykgtL0dHIYTjrU5U8cd5jaPDEGXwWBMTzQMqJuUYNGgQWq22QvbtDGpky9zUqVOZPHmy9XZFtczVa53O2s3b6NixIy4uNbKqK43RaGT37t3Vtq6fXLifq7kGunbrTvNIX4fGYjAYWLNmDX379q3RB8/KIHVdstPrT8P50/RuEsKj3evZZZ/V/TjiaA1CvPH3tLyP7f3erun/XzXy1bu5uZUrcTOZTGVqEq4f5E5ioAttavnIgbiCGQwGMk87rq61Wi0aTcW1ErhrNYABjcalyryXtFptlYmlupO6Lkqttlz1CPfzILZBqF32aTAYSDsOXWJCpL4riby37aNGJnNlpSgKycnJpKenl3m78PBwzp8/X6Ov5VeGqlDX/v7+hIeHV8jzq6/t01y9ekUIUW7max8FtRxbhageyVx2djanTp2y3o6Pj2f//v0EBgZSp06dW95/QSIXGhqKp6dnqU/WZrOZ7OxsvL29rd8iRcVwZF1bOk3nkpqaCkBERITdn6PgLSfJnBAWBd291ZLLCVE9krk9e/bQq1cv6+2C/nCjRo1i/vz5t7Rvk8lkTeSCgoLKtK3ZbEav1+Pu7i7JXAVzdF17eHgAkJqaSmhoqN0vuRYkc5LKCWFR8L1GrnoIUU2SuZ49e1bYLNIFfeQ8PT0rZP+i+ih4jxgMBrsncwWXkqrZ4HMhyq2glVpyOSGqyQoQlUG+/Ymbqcj3SMGeJZcTwqLgo6BCjs1CSDInhBMoHADh4ECEqCLM0mdOCCtJ5oRwAjIAQghbhX3mHBuHEFWBJHPiltSrV4+PP/74hmVUKhW//PJLpcRTXamsfeYcHIgQVUThaFbJ5oSQZE44lWnTptGmTRtHh1HpCi4lyQAIISzMMppVCCtJ5oRwAgWdvCWVE8JCLrMKUUiSuTJSFIVcvbHUP3l6U5nK3+inrK0yZrOZGTNmEB0djYeHB61bt2bJkiUAbNiwAZVKxdq1a+nQoQOenp507dqV48ePW7c/ffo099xzD2FhYXh7e9OxY0f++uuvIs+TlZXFyJEj8fLyolatWsyaNeuGcZ0/f57hw4fj7+9PYGAg99xzD2fPni3TayuNDRs20KlTJ7y8vPD396dbt26cO3eOs2fPolar2bNnj035jz/+mLp162I2m0tVP5VJ+swJYUsGQAhRqFrMM1eZ8gwmmr2+yiHPffTN/ni6lv6/bMaMGXz//ffMnj2bhg0bsmnTJh566CFCQkKsZV555RU+/PBDQkJCePzxxxk7dixbt24FLCtrDBo0iHfeeQc3Nze+/fZbBg8ezPHjx21W1nj//fd5+eWXmT59OqtWreKZZ56hUaNG9O3bt0hMBoOB/v37Exsby+bNm3FxceHtt99mwIABHDx4EFdX11uooUJGo5F7772X8ePHs2jRIvR6Pbt27UKlUlG3bl369OnDvHnz6NChg3WbefPmMXr0aJtJh29UP5VJRrMKYavgy61MTSKEJHPVlk6n4z//+Q9//fUXsbGxANSvX58tW7bw1Vdf8dhjjwHwzjvvcPvttwPw0ksvceedd5Kfn4+7uzutW7emdevW1n2+9dZbLFu2jF9//ZWJEyda7+/WrRsvvfQSAI0aNWLr1q189NFHxSZzixcvxmw2880331j7usybNw9/f382bNhAv3797PL6MzMzycjI4K677iImJgaApk2bWh9/9NFHefzxx5k5cyZubm7s27ePQ4cOsXz5cpv93Kh+KpNK+swJYaPgkyAtc0JIMldmHloNR9/sX6qyZrOZrMwsfHx97LLElIe29KsKnDp1itzc3CIJlV6vp23bttbbrVq1sv5dsKZoamoqderUITs7m2nTpvHHH3+QlJSE0WgkLy+PhIQEm30WJIvX3y5phOuBAwc4deoUPj4+Nvfn5+dz+vTpUr++mwkMDGT06NH079+fvn370qdPH4YPH259jffeey8TJkxg2bJl3H///cyfP59evXpRr149m/3cqH4qk1pGswpho3AFCMnmhJBkroxUKlWpL3WazWaMrho8XV0qfb3Q7OxsAP744w9q1apl85ibm5s1cdJqtdb7Cw6KZrMZgOeff541a9bwwQcf0KBBAzw8PLjvvvvQ6/W3FFf79u354Ycfijx2/eVfe5g3bx5PP/00K1euZPHixbz66qusWbOGLl264OrqyiOPPMK8efMYOnQoCxcu5JNPPimyjxvVT2WyjmaVIRBCADIAQojrSTJXTTVr1gw3NzcSEhKslwmvV5pWsK1btzJ69GiGDBkCWBKx4gYq7Nixo8jt6y9pXq9du3YsXryY0NBQfH19S/FKbk3btm1p27YtU6dOJTY2loULF9KlSxfAcqm1RYsWfPHFFxiNRoYOHVrh8ZSbNZF0cBxCVBEF/UdlnjkhZDRrteXj48Pzzz/Ps88+y4IFCzh9+jT79u3js88+Y8GCBaXaR8OGDVm6dCn79+/nwIEDPPDAA8W2Sm3dupX33nuPEydOMGvWLH7++WeeeeaZYvf54IMPEhwczD333MPmzZuJj49nw4YNPP300yQmJt7Sa75efHw8U6dOZfv27Zw7d47Vq1dz8uRJmySzadOmdOnShSlTpjBy5Eg8PDzs9vz2ppbRrELYKBwAIYSQlrlq7K233iIkJIQZM2Zw5swZ/P39adeuHS+//HKpLhXOnDmTsWPH0rVrV4KDg5kyZQqZmZlFyj333HPs2bOH6dOn4+vry8yZM+nfv/h+hZ6enmzatIkpU6YwdOhQsrKyqFWrFr1797ZrS52n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5JNP6Nu3r8M7m4viubu7M2XKFF588UVcXV3p1q0bly5d4siRIzzyyCPUrl2badOm8c4773DixAk+/PDDcj/XlClT6NKlCxMnTuTRRx/Fy8uLo0ePsmbNGj7//PNS7SM0NBQPDw9WrlxJVFQU7u7u+Pn5lTum6kaRljkhbKikz5wQVmXOxOrWrcuWLVvYtGkTJ06cqIiYhJ289tprPPfcc7z++us0bdqUESNGkJqailarZdGiRcTFxdGqVSveffdd3n777XI/T6tWrdi4cSMnTpzgtttuo23btrz++utERkaWeh8uLi58+umnfPXVV0RGRnLPPfeUO57qSPrMCWFL+swJUajMLXNxcXHWvnIdO3akUaNGPPTQQ4CcaKoatVrNK6+8wiuvvFLksW7duhVZnu36y96jR49m9OjRRbY7e/Zssc/VsWNHVq9eXWIsxW33z0u7jz76aJlG29YkJrOMZhXiegWt1NV1VRwhyqJc10i7devG3LlzSUpK4vHHH+fnn3/GZDLx5JNP8vXXX3Pp0iV7xylEjSaXWYWwJVOTCFHoljq8eXt7M378eLZt28aRI0do164dr776apkurwkhbs4s88wJYUMGQAhRyG6jF5o2bcqHH35IYmIiixcvttduhRBInzkh/qnwMquDAxGiCrileeZMJhPLli3j2LFjADRr1ox77rmHoUOH2iU4IYRFwaUkjSRzQgDSMifE9cqdzB05coS7776b5ORkGjduDMC7775LSEgIv//+O82bN7dbkFWBdLIVpVUR7xXrcl4yE5AQgLTMCXG9cp8aHn30UZo3b05iYiL79u1j3759nD9/nlatWjF+/Hh7xuhQBasW5ObmOjgS4SwK3iv2XDy6oGVOLrMKYSEtc0IUKnfL3P79+9mzZ491BQGAgIAA3nnnHTp27GiX4KoCjUaDv7+/dQ1RT0/PUp9QzWYzer2e/Px8mVy5glWFulYUhdzcXFJTU/H390ej0dht34VTk0gyJwTIaFYhrlfuZK5Ro0akpKQUuZyampp6S8s6VUXh4eEAZV4UXlEU8vLySlzjVNhPVaprf39/63vGXmQ0qxC2ZNJgIQqVO5mbMWMGTz/9NNOmTaNLly4A7NixgzfffJN3332XzMxMa1lfX99bj9SBVCoVERERhIaGFrsgfUkMBgObNm2iR48edr3kJoqqKnWt1Wrt2iJX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", "text/plain": [ "
" ] @@ -735,6 +743,7 @@ ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ diff --git a/doc/tutorials/stdp_third_factor_active_dendrite/stdp_third_factor_active_dendrite.ipynb b/doc/tutorials/stdp_third_factor_active_dendrite/stdp_third_factor_active_dendrite.ipynb index 147f5b4a1..3922d0ac8 100644 --- a/doc/tutorials/stdp_third_factor_active_dendrite/stdp_third_factor_active_dendrite.ipynb +++ b/doc/tutorials/stdp_third_factor_active_dendrite/stdp_third_factor_active_dendrite.ipynb @@ -1347,7 +1347,7 @@ " NESTCodeGeneratorUtils.generate_code_for(nestml_neuron_model,\n", " nestml_synapse_model,\n", " codegen_opts=codegen_opts,\n", - " logging_level=\"INFO\") # try \"INFO\" or \"DEBUG\" for more debug information" + " logging_level=\"WARNING\") # try \"INFO\" or \"DEBUG\" for more debug information" ] }, { diff --git a/pynestml/cocos/co_co_all_variables_defined.py b/pynestml/cocos/co_co_all_variables_defined.py index e41b0727e..38cfa89ab 100644 --- a/pynestml/cocos/co_co_all_variables_defined.py +++ b/pynestml/cocos/co_co_all_variables_defined.py @@ -41,11 +41,10 @@ class CoCoAllVariablesDefined(CoCo): """ @classmethod - def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False): + def check_co_co(cls, node: ASTModel): """ Checks if this coco applies for the handed over neuron. Models which contain undefined variables are not correct. :param node: a single neuron instance. - :param after_ast_rewrite: indicates whether this coco is checked after the code generator has done rewriting of the abstract syntax tree. If True, checks are not as rigorous. Use False where possible. """ # for each variable in all expressions, check if the variable has been defined previously expression_collector_visitor = ASTExpressionCollectorVisitor() @@ -62,32 +61,6 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False): # test if the symbol has been defined at least if symbol is None: - if after_ast_rewrite: # after ODE-toolbox transformations, convolutions are replaced by state variables, so cannot perform this check properly - symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE) - if symbol2 is not None: - # an inline expression defining this variable name (ignoring differential order) exists - if "__X__" in str(symbol2): # if this variable was the result of a convolution... - continue - else: - # for kernels, also allow derivatives of that kernel to appear - - inline_expr_names = [] - inline_exprs = [] - for equations_block in node.get_equations_blocks(): - inline_expr_names.extend([inline_expr.variable_name for inline_expr in equations_block.get_inline_expressions()]) - inline_exprs.extend(equations_block.get_inline_expressions()) - - if var.get_name() in inline_expr_names: - inline_expr_idx = inline_expr_names.index(var.get_name()) - inline_expr = inline_exprs[inline_expr_idx] - from pynestml.utils.ast_utils import ASTUtils - if ASTUtils.inline_aliases_convolution(inline_expr): - symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE) - if symbol2 is not None: - # actually, no problem detected, skip error - # XXX: TODO: check that differential order is less than or equal to that of the kernel - continue - # check if this symbol is actually a type, e.g. "mV" in the expression "(1 + 2) * mV" symbol2 = var.get_scope().resolve_to_symbol(var.get_complete_name(), SymbolKind.TYPE) if symbol2 is not None: @@ -106,9 +79,14 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False): # in this case its ok if it is recursive or defined later on continue + if symbol.is_predefined: + continue + + if symbol.block_type == BlockType.LOCAL and symbol.get_referenced_object().get_source_position().before(var.get_source_position()): + continue + # check if it has been defined before usage, except for predefined symbols, input ports and variables added by the AST transformation functions - if (not symbol.is_predefined) \ - and symbol.block_type != BlockType.INPUT \ + if symbol.block_type != BlockType.INPUT \ and not symbol.get_referenced_object().get_source_position().is_added_source_position(): # except for parameters, those can be defined after if ((not symbol.get_referenced_object().get_source_position().before(var.get_source_position())) diff --git a/pynestml/cocos/co_co_function_unique.py b/pynestml/cocos/co_co_function_unique.py index 15643c0ad..bf0f2be60 100644 --- a/pynestml/cocos/co_co_function_unique.py +++ b/pynestml/cocos/co_co_function_unique.py @@ -65,4 +65,5 @@ def check_co_co(cls, model: ASTModel): log_level=LoggingLevel.ERROR, message=message, code=code) checked.append(funcA) + checked_funcs_names.append(func.get_name()) diff --git a/pynestml/cocos/co_co_illegal_expression.py b/pynestml/cocos/co_co_illegal_expression.py index b78396e3b..c362d0dc5 100644 --- a/pynestml/cocos/co_co_illegal_expression.py +++ b/pynestml/cocos/co_co_illegal_expression.py @@ -18,13 +18,13 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from pynestml.meta_model.ast_inline_expression import ASTInlineExpression -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.meta_model.ast_declaration import ASTDeclaration from pynestml.cocos.co_co import CoCo +from pynestml.meta_model.ast_declaration import ASTDeclaration +from pynestml.meta_model.ast_inline_expression import ASTInlineExpression from pynestml.symbols.error_type_symbol import ErrorTypeSymbol from pynestml.symbols.predefined_types import PredefinedTypes +from pynestml.utils.ast_source_location import ASTSourceLocation from pynestml.utils.logger import LoggingLevel, Logger from pynestml.utils.logging_helper import LoggingHelper from pynestml.utils.messages import Messages diff --git a/pynestml/cocos/co_co_no_kernels_except_in_convolve.py b/pynestml/cocos/co_co_no_kernels_except_in_convolve.py index 18b862292..e318ae566 100644 --- a/pynestml/cocos/co_co_no_kernels_except_in_convolve.py +++ b/pynestml/cocos/co_co_no_kernels_except_in_convolve.py @@ -22,11 +22,14 @@ from typing import List from pynestml.cocos.co_co import CoCo +from pynestml.meta_model.ast_declaration import ASTDeclaration +from pynestml.meta_model.ast_external_variable import ASTExternalVariable from pynestml.meta_model.ast_function_call import ASTFunctionCall from pynestml.meta_model.ast_kernel import ASTKernel from pynestml.meta_model.ast_model import ASTModel from pynestml.meta_model.ast_node import ASTNode from pynestml.meta_model.ast_variable import ASTVariable +from pynestml.symbols.predefined_functions import PredefinedFunctions from pynestml.symbols.symbol import SymbolKind from pynestml.utils.logger import Logger, LoggingLevel from pynestml.utils.messages import Messages @@ -89,24 +92,44 @@ def visit_variable(self, node: ASTNode): if not (isinstance(node, ASTExternalVariable) and node.get_alternate_name()): code, message = Messages.get_no_variable_found(kernelName) Logger.log_message(node=self.__neuron_node, code=code, message=message, log_level=LoggingLevel.ERROR) + continue + if not symbol.is_kernel(): continue + if node.get_complete_name() == kernelName: - parent = node.get_parent() - if parent is not None: + parent = node + correct = False + while parent is not None and not isinstance(parent, ASTModel): + parent = parent.get_parent() + assert parent is not None + + if isinstance(parent, ASTDeclaration): + for lhs_var in parent.get_variables(): + if kernelName == lhs_var.get_complete_name(): + # kernel name appears on lhs of declaration, assume it is initial state + correct = True + parent = None # break out of outer loop + break + if isinstance(parent, ASTKernel): - continue - grandparent = parent.get_parent() - if grandparent is not None and isinstance(grandparent, ASTFunctionCall): - grandparent_func_name = grandparent.get_name() - if grandparent_func_name == 'convolve': - continue - code, message = Messages.get_kernel_outside_convolve(kernelName) - Logger.log_message(code=code, - message=message, - log_level=LoggingLevel.ERROR, - error_position=node.get_source_position()) + # kernel name is used inside kernel definition, e.g. for a node ``g``, it appears in ``kernel g'' = -1/tau**2 * g - 2/tau * g'`` + correct = True + break + + if isinstance(parent, ASTFunctionCall): + func_name = parent.get_name() + if func_name == PredefinedFunctions.CONVOLVE: + # kernel name is used inside convolve call + correct = True + + if not correct: + code, message = Messages.get_kernel_outside_convolve(kernelName) + Logger.log_message(code=code, + message=message, + log_level=LoggingLevel.ERROR, + error_position=node.get_source_position()) class KernelCollectingVisitor(ASTVisitor): diff --git a/pynestml/cocos/co_co_odes_have_consistent_units.py b/pynestml/cocos/co_co_odes_have_consistent_units.py index 76a73d6f8..2922e3b52 100644 --- a/pynestml/cocos/co_co_odes_have_consistent_units.py +++ b/pynestml/cocos/co_co_odes_have_consistent_units.py @@ -50,6 +50,7 @@ def visit_ode_equation(self, node): :param node: A single ode equation. :type node: ast_ode_equation """ + return variable_name = node.get_lhs().get_name() variable_symbol = node.get_lhs().get_scope().resolve_to_symbol(variable_name, SymbolKind.VARIABLE) if variable_symbol is None: diff --git a/pynestml/cocos/co_co_v_comp_exists.py b/pynestml/cocos/co_co_v_comp_exists.py index 4ef08c0ec..51308f2cc 100644 --- a/pynestml/cocos/co_co_v_comp_exists.py +++ b/pynestml/cocos/co_co_v_comp_exists.py @@ -43,9 +43,6 @@ def check_co_co(cls, neuron: ASTModel): Models which are supposed to be compartmental but do not contain state variable called v_comp are not correct. :param neuron: a single neuron instance. - :param after_ast_rewrite: indicates whether this coco is checked - after the code generator has done rewriting of the abstract syntax tree. - If True, checks are not as rigorous. Use False where possible. """ from pynestml.codegeneration.nest_compartmental_code_generator import NESTCompartmentalCodeGenerator diff --git a/pynestml/cocos/co_cos_manager.py b/pynestml/cocos/co_cos_manager.py index 01d008890..9ad3b37bf 100644 --- a/pynestml/cocos/co_cos_manager.py +++ b/pynestml/cocos/co_cos_manager.py @@ -69,6 +69,7 @@ from pynestml.cocos.co_co_priorities_correctly_specified import CoCoPrioritiesCorrectlySpecified from pynestml.meta_model.ast_model import ASTModel from pynestml.frontend.frontend_configuration import FrontendConfiguration +from pynestml.utils.logger import Logger class CoCosManager: @@ -123,12 +124,12 @@ def check_state_variables_initialized(cls, model: ASTModel): CoCoStateVariablesInitialized.check_co_co(model) @classmethod - def check_variables_defined_before_usage(cls, model: ASTModel, after_ast_rewrite: bool) -> None: + def check_variables_defined_before_usage(cls, model: ASTModel) -> None: """ Checks that all variables are defined before being used. :param model: a single model. """ - CoCoAllVariablesDefined.check_co_co(model, after_ast_rewrite) + CoCoAllVariablesDefined.check_co_co(model) @classmethod def check_v_comp_requirement(cls, neuron: ASTModel): @@ -402,17 +403,19 @@ def check_input_port_size_type(cls, model: ASTModel): CoCoVectorInputPortsCorrectSizeType.check_co_co(model) @classmethod - def post_symbol_table_builder_checks(cls, model: ASTModel, after_ast_rewrite: bool = False): + def check_cocos(cls, model: ASTModel, after_ast_rewrite: bool = False): """ Checks all context conditions. :param model: a single model object. """ + Logger.set_current_node(model) + cls.check_each_block_defined_at_most_once(model) cls.check_function_defined(model) cls.check_variables_unique_in_scope(model) cls.check_inline_expression_not_assigned_to(model) cls.check_state_variables_initialized(model) - cls.check_variables_defined_before_usage(model, after_ast_rewrite) + cls.check_variables_defined_before_usage(model) if FrontendConfiguration.get_target_platform().upper() == 'NEST_COMPARTMENTAL': # XXX: TODO: refactor this out; define a ``cocos_from_target_name()`` in the frontend instead. cls.check_v_comp_requirement(model) @@ -452,3 +455,5 @@ def post_symbol_table_builder_checks(cls, model: ASTModel, after_ast_rewrite: bo cls.check_co_co_priorities_correctly_specified(model) cls.check_resolution_func_legally_used(model) cls.check_input_port_size_type(model) + + Logger.set_current_node(None) diff --git a/pynestml/codegeneration/builder.py b/pynestml/codegeneration/builder.py index 2e6757c1a..a9f98bf58 100644 --- a/pynestml/codegeneration/builder.py +++ b/pynestml/codegeneration/builder.py @@ -20,12 +20,12 @@ # along with NEST. If not, see . from __future__ import annotations -import subprocess -import os from typing import Any, Mapping, Optional from abc import ABCMeta, abstractmethod +import os +import subprocess from pynestml.exceptions.invalid_target_exception import InvalidTargetException from pynestml.frontend.frontend_configuration import FrontendConfiguration diff --git a/pynestml/codegeneration/code_generator.py b/pynestml/codegeneration/code_generator.py index 1c463efb4..c953fd562 100644 --- a/pynestml/codegeneration/code_generator.py +++ b/pynestml/codegeneration/code_generator.py @@ -113,7 +113,6 @@ def _setup_template_env(self, template_files: List[str], templates_root_dir: str # Environment for neuron templates env = Environment(loader=FileSystemLoader(_template_dirs)) env.globals["raise"] = self.raise_helper - env.globals["is_delta_kernel"] = ASTUtils.is_delta_kernel # Load all the templates _templates = list() diff --git a/pynestml/codegeneration/nest_code_generator.py b/pynestml/codegeneration/nest_code_generator.py index 0551e9a6e..c108caa4d 100644 --- a/pynestml/codegeneration/nest_code_generator.py +++ b/pynestml/codegeneration/nest_code_generator.py @@ -28,6 +28,7 @@ import pynestml from pynestml.cocos.co_co_nest_synapse_delay_not_assigned_to import CoCoNESTSynapseDelayNotAssignedTo +from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.codegeneration.code_generator import CodeGenerator from pynestml.codegeneration.code_generator_utils import CodeGeneratorUtils from pynestml.codegeneration.nest_assignments_helper import NestAssignmentsHelper @@ -53,7 +54,6 @@ from pynestml.frontend.frontend_configuration import FrontendConfiguration from pynestml.meta_model.ast_assignment import ASTAssignment from pynestml.meta_model.ast_input_port import ASTInputPort -from pynestml.meta_model.ast_kernel import ASTKernel from pynestml.meta_model.ast_model import ASTModel from pynestml.meta_model.ast_node_factory import ASTNodeFactory from pynestml.meta_model.ast_ode_equation import ASTOdeEquation @@ -74,6 +74,7 @@ from pynestml.visitors.ast_equations_with_delay_vars_visitor import ASTEquationsWithDelayVarsVisitor from pynestml.visitors.ast_equations_with_vector_variables import ASTEquationsWithVectorVariablesVisitor from pynestml.visitors.ast_mark_delay_vars_visitor import ASTMarkDelayVarsVisitor +from pynestml.visitors.ast_parent_visitor import ASTParentVisitor from pynestml.visitors.ast_set_vector_parameter_in_update_expressions import \ ASTSetVectorParameterInUpdateExpressionVisitor from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor @@ -172,6 +173,10 @@ def __init__(self, options: Optional[Mapping[str, Any]] = None): self.setup_printers() def run_nest_target_specific_cocos(self, neurons: Sequence[ASTModel], synapses: Sequence[ASTModel]): + for model in neurons + synapses: + for equations_block in model.get_equations_blocks(): + assert len(equations_block.get_kernels()) == 0, "Kernels and convolutions should have been removed by ConvolutionsTransformer" + for synapse in synapses: synapse_name_stripped = removesuffix(removesuffix(synapse.name.split("_with_")[0], "_"), FrontendConfiguration.suffix) @@ -295,9 +300,7 @@ def analyse_transform_neurons(self, neurons: List[ASTModel]) -> None: for neuron in neurons: code, message = Messages.get_analysing_transforming_model(neuron.get_name()) Logger.log_message(None, code, message, None, LoggingLevel.INFO) - spike_updates, post_spike_updates, equations_with_delay_vars, equations_with_vector_vars = self.analyse_neuron(neuron) - neuron.spike_updates = spike_updates - neuron.post_spike_updates = post_spike_updates + equations_with_delay_vars, equations_with_vector_vars = self.analyse_neuron(neuron) neuron.equations_with_delay_vars = equations_with_delay_vars neuron.equations_with_vector_vars = equations_with_vector_vars @@ -308,14 +311,12 @@ def analyse_transform_synapses(self, synapses: List[ASTModel]) -> None: """ for synapse in synapses: Logger.log_message(None, None, "Analysing/transforming synapse {}.".format(synapse.get_name()), None, LoggingLevel.INFO) - synapse.spike_updates = self.analyse_synapse(synapse) + self.analyse_synapse(synapse) def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Dict[str, ASTAssignment], List[ASTOdeEquation], List[ASTOdeEquation]]: """ Analyse and transform a single neuron. :param neuron: a single neuron. - :return: see documentation for get_spike_update_expressions() for more information. - :return: post_spike_updates: list of post-synaptic spike update expressions :return: equations_with_delay_vars: list of equations containing delay variables :return: equations_with_vector_vars: list of equations containing delay variables """ @@ -329,17 +330,14 @@ def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Di ASTUtils.all_variables_defined_in_block(neuron.get_state_blocks())) ASTUtils.add_timestep_symbol(neuron) - return {}, {}, [], [] + return [], [] if len(neuron.get_equations_blocks()) > 1: raise Exception("Only one equations block per model supported for now") equations_block = neuron.get_equations_blocks()[0] - kernel_buffers = ASTUtils.generate_kernel_buffers(neuron, equations_block) InlineExpressionExpansionTransformer().transform(neuron) - delta_factors = ASTUtils.get_delta_factors_(neuron, equations_block) - ASTUtils.replace_convolve_calls_with_buffers_(neuron, equations_block) # Collect all equations with delay variables and replace ASTFunctionCall to ASTVariable wherever necessary equations_with_delay_vars_visitor = ASTEquationsWithDelayVarsVisitor() @@ -351,7 +349,7 @@ def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Di neuron.accept(eqns_with_vector_vars_visitor) equations_with_vector_vars = eqns_with_vector_vars_visitor.equations - analytic_solver, numeric_solver = self.ode_toolbox_analysis(neuron, kernel_buffers) + analytic_solver, numeric_solver = self.ode_toolbox_analysis(neuron) self.analytic_solver[neuron.get_name()] = analytic_solver self.numeric_solver[neuron.get_name()] = numeric_solver @@ -365,38 +363,29 @@ def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Di if ode_eq.get_lhs().get_name() == var.get_name(): used_in_eq = True break - for kern in equations_block.get_kernels(): - for kern_var in kern.get_variables(): - if kern_var.get_name() == var.get_name(): - used_in_eq = True - break if not used_in_eq: self.non_equations_state_variables[neuron.get_name()].append(var) - ASTUtils.remove_initial_values_for_kernels(neuron) - kernels = ASTUtils.remove_kernel_definitions_from_equations_block(neuron) + # cache state variables before symbol table update for the sake of delay variables + state_vars_before_update = neuron.get_state_symbols() + ASTUtils.update_initial_values_for_odes(neuron, [analytic_solver, numeric_solver]) ASTUtils.remove_ode_definitions_from_equations_block(neuron) - ASTUtils.create_initial_values_for_kernels(neuron, [analytic_solver, numeric_solver], kernels) ASTUtils.create_integrate_odes_combinations(neuron) ASTUtils.replace_variable_names_in_expressions(neuron, [analytic_solver, numeric_solver]) - ASTUtils.replace_convolution_aliasing_inlines(neuron) ASTUtils.add_timestep_symbol(neuron) if self.analytic_solver[neuron.get_name()] is not None: neuron = ASTUtils.add_declarations_to_internals( neuron, self.analytic_solver[neuron.get_name()]["propagators"]) - state_vars_before_update = neuron.get_state_symbols() self.update_symbol_table(neuron) # Update the delay parameter parameters after symbol table update ASTUtils.update_delay_parameter_in_state_vars(neuron, state_vars_before_update) - spike_updates, post_spike_updates = self.get_spike_update_expressions(neuron, kernel_buffers, [analytic_solver, numeric_solver], delta_factors) - - return spike_updates, post_spike_updates, equations_with_delay_vars, equations_with_vector_vars + return equations_with_delay_vars, equations_with_vector_vars def analyse_synapse(self, synapse: ASTModel) -> Dict[str, ASTAssignment]: """ @@ -406,32 +395,22 @@ def analyse_synapse(self, synapse: ASTModel) -> Dict[str, ASTAssignment]: code, message = Messages.get_start_processing_model(synapse.get_name()) Logger.log_message(synapse, code, message, synapse.get_source_position(), LoggingLevel.INFO) - spike_updates = {} if synapse.get_equations_blocks(): if len(synapse.get_equations_blocks()) > 1: raise Exception("Only one equations block per model supported for now") - equations_block = synapse.get_equations_blocks()[0] - - kernel_buffers = ASTUtils.generate_kernel_buffers(synapse, equations_block) InlineExpressionExpansionTransformer().transform(synapse) - delta_factors = ASTUtils.get_delta_factors_(synapse, equations_block) - ASTUtils.replace_convolve_calls_with_buffers_(synapse, equations_block) - analytic_solver, numeric_solver = self.ode_toolbox_analysis(synapse, kernel_buffers) + analytic_solver, numeric_solver = self.ode_toolbox_analysis(synapse) self.analytic_solver[synapse.get_name()] = analytic_solver self.numeric_solver[synapse.get_name()] = numeric_solver - ASTUtils.remove_initial_values_for_kernels(synapse) - kernels = ASTUtils.remove_kernel_definitions_from_equations_block(synapse) ASTUtils.update_initial_values_for_odes(synapse, [analytic_solver, numeric_solver]) ASTUtils.remove_ode_definitions_from_equations_block(synapse) - ASTUtils.create_initial_values_for_kernels(synapse, [analytic_solver, numeric_solver], kernels) ASTUtils.create_integrate_odes_combinations(synapse) ASTUtils.replace_variable_names_in_expressions(synapse, [analytic_solver, numeric_solver]) ASTUtils.add_timestep_symbol(synapse) self.update_symbol_table(synapse) - spike_updates, _ = self.get_spike_update_expressions(synapse, kernel_buffers, [analytic_solver, numeric_solver], delta_factors) if not self.analytic_solver[synapse.get_name()] is None: synapse = ASTUtils.add_declarations_to_internals( @@ -444,8 +423,6 @@ def analyse_synapse(self, synapse: ASTModel) -> Dict[str, ASTAssignment]: ASTUtils.update_blocktype_for_common_parameters(synapse) - return spike_updates - def _get_model_namespace(self, astnode: ASTModel) -> Dict: namespace = {} @@ -614,8 +591,6 @@ def _get_synapse_model_namespace(self, synapse: ASTModel) -> Dict: expr_ast.accept(ASTSymbolTableVisitor()) namespace["numeric_update_expressions"][sym] = expr_ast - namespace["spike_updates"] = synapse.spike_updates - synapse_name_stripped = removesuffix(removesuffix(synapse.name.split("_with_")[0], "_"), FrontendConfiguration.suffix) # special case for NEST delay variable (state or parameter) @@ -654,7 +629,6 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: namespace["paired_synapse"] = neuron.paired_synapse namespace["paired_synapse_original_model"] = neuron.paired_synapse_original_model namespace["paired_synapse_name"] = neuron.paired_synapse.get_name() - namespace["post_spike_updates"] = neuron.post_spike_updates namespace["transferred_variables"] = neuron._transferred_variables namespace["transferred_variables_syms"] = {var_name: neuron.scope.resolve_to_symbol( var_name, SymbolKind.VARIABLE) for var_name in namespace["transferred_variables"]} @@ -806,8 +780,6 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: numeric_state_variable_names.extend(namespace["analytic_state_variables_moved"]) namespace["numerical_state_symbols"] = numeric_state_variable_names ASTUtils.assign_numeric_non_numeric_state_variables(neuron, numeric_state_variable_names, namespace["numeric_update_expressions"] if "numeric_update_expressions" in namespace.keys() else None, namespace["update_expressions"] if "update_expressions" in namespace.keys() else None) - namespace["spike_updates"] = neuron.spike_updates - namespace["recordable_state_variables"] = [] for state_block in neuron.get_state_blocks(): for decl in state_block.get_declarations(): @@ -815,7 +787,6 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: sym = var.get_scope().resolve_to_symbol(var.get_complete_name(), SymbolKind.VARIABLE) if isinstance(sym.get_type_symbol(), (UnitTypeSymbol, RealTypeSymbol)) \ - and not ASTUtils.is_delta_kernel(neuron.get_kernel_by_name(sym.name)) \ and sym.is_recordable: namespace["recordable_state_variables"].append(var) @@ -825,7 +796,7 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: for var in decl.get_variables(): sym = var.get_scope().resolve_to_symbol(var.get_complete_name(), SymbolKind.VARIABLE) - if sym.has_declaring_expression() and (not neuron.get_kernel_by_name(sym.name)): + if sym.has_declaring_expression(): namespace["parameter_vars_with_iv"].append(var) namespace["recordable_inline_expressions"] = [sym for sym in neuron.get_inline_expression_symbols() @@ -844,7 +815,7 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: return namespace - def ode_toolbox_analysis(self, neuron: ASTModel, kernel_buffers: Mapping[ASTKernel, ASTInputPort]): + def ode_toolbox_analysis(self, neuron: ASTModel): """ Prepare data for ODE-toolbox input format, invoke ODE-toolbox analysis via its API, and return the output. """ @@ -853,11 +824,11 @@ def ode_toolbox_analysis(self, neuron: ASTModel, kernel_buffers: Mapping[ASTKern equations_block = neuron.get_equations_blocks()[0] - if len(equations_block.get_kernels()) == 0 and len(equations_block.get_ode_equations()) == 0: + if len(equations_block.get_ode_equations()) == 0: # no equations defined -> no changes to the neuron return None, None - odetoolbox_indict = ASTUtils.transform_ode_and_kernels_to_json(neuron, neuron.get_parameters_blocks(), kernel_buffers, printer=self._ode_toolbox_printer) + odetoolbox_indict = ASTUtils.transform_odes_to_json(neuron, neuron.get_parameters_blocks(), printer=self._ode_toolbox_printer) odetoolbox_indict["options"] = {} odetoolbox_indict["options"]["output_timestep_symbol"] = "__h" disable_analytic_solver = self.get_option("solver") != "analytic" @@ -896,112 +867,10 @@ def update_symbol_table(self, neuron) -> None: """ Update symbol table and scope. """ + neuron.accept(ASTParentVisitor()) + SymbolTable.delete_model_scope(neuron.get_name()) symbol_table_visitor = ASTSymbolTableVisitor() - symbol_table_visitor.after_ast_rewrite_ = True neuron.accept(symbol_table_visitor) + CoCosManager.check_cocos(neuron, after_ast_rewrite=True) SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope()) - - def get_spike_update_expressions(self, neuron: ASTModel, kernel_buffers, solver_dicts, delta_factors) -> Tuple[Dict[str, ASTAssignment], Dict[str, ASTAssignment]]: - r""" - Generate the equations that update the dynamical variables when incoming spikes arrive. To be invoked after - ode-toolbox. - - For example, a resulting `assignment_str` could be "I_kernel_in += (inh_spikes/nS) * 1". The values are taken from the initial values for each corresponding dynamical variable, either from ode-toolbox or directly from user specification in the model. - from the initial values for each corresponding dynamical variable, either from ode-toolbox or directly from - user specification in the model. - - Note that for kernels, `initial_values` actually contains the increment upon spike arrival, rather than the - initial value of the corresponding ODE dimension. - ``spike_updates`` is a mapping from input port name (as a string) to update expressions. - - ``post_spike_updates`` is a mapping from kernel name (as a string) to update expressions. - """ - spike_updates = {} - post_spike_updates = {} - - for kernel, spike_input_port in kernel_buffers: - if ASTUtils.is_delta_kernel(kernel): - continue - - spike_input_port_name = spike_input_port.get_variable().get_name() - - if not spike_input_port_name in spike_updates.keys(): - spike_updates[str(spike_input_port)] = [] - - if "_is_post_port" in dir(spike_input_port.get_variable()) \ - and spike_input_port.get_variable()._is_post_port: - # it's a port in the neuron ??? that receives post spikes ??? - orig_port_name = spike_input_port_name[:spike_input_port_name.index("__for_")] - buffer_type = neuron.paired_synapse.get_scope().resolve_to_symbol(orig_port_name, SymbolKind.VARIABLE).get_type_symbol() - else: - buffer_type = neuron.get_scope().resolve_to_symbol(spike_input_port_name, SymbolKind.VARIABLE).get_type_symbol() - - assert not buffer_type is None - - for kernel_var in kernel.get_variables(): - for var_order in range(ASTUtils.get_kernel_var_order_from_ode_toolbox_result(kernel_var.get_name(), solver_dicts)): - kernel_spike_buf_name = ASTUtils.construct_kernel_X_spike_buf_name(kernel_var.get_name(), spike_input_port, var_order) - expr = ASTUtils.get_initial_value_from_ode_toolbox_result(kernel_spike_buf_name, solver_dicts) - assert expr is not None, "Initial value not found for kernel " + kernel_var - expr = str(expr) - if expr in ["0", "0.", "0.0"]: - continue # skip adding the statement if we are only adding zero - - assignment_str = kernel_spike_buf_name + " += " - if "_is_post_port" in dir(spike_input_port.get_variable()) \ - and spike_input_port.get_variable()._is_post_port: - assignment_str += "1." - else: - assignment_str += "(" + str(spike_input_port) + ")" - if not expr in ["1.", "1.0", "1"]: - assignment_str += " * (" + expr + ")" - - if not buffer_type.print_nestml_type() in ["1.", "1.0", "1", "real", "integer"]: - assignment_str += " / (" + buffer_type.print_nestml_type() + ")" - - ast_assignment = ModelParser.parse_assignment(assignment_str) - ast_assignment.update_scope(neuron.get_scope()) - ast_assignment.accept(ASTSymbolTableVisitor()) - - if neuron.get_scope().resolve_to_symbol(spike_input_port_name, SymbolKind.VARIABLE) is None: - # this case covers variables that were moved from synapse to the neuron - post_spike_updates[kernel_var.get_name()] = ast_assignment - elif "_is_post_port" in dir(spike_input_port.get_variable()) and spike_input_port.get_variable()._is_post_port: - Logger.log_message(None, None, "Adding post assignment string: " + str(ast_assignment), None, LoggingLevel.INFO) - spike_updates[str(spike_input_port)].append(ast_assignment) - else: - spike_updates[str(spike_input_port)].append(ast_assignment) - - for k, factor in delta_factors.items(): - var = k[0] - inport = k[1] - assignment_str = var.get_name() + "'" * (var.get_differential_order() - 1) + " += " - if not factor in ["1.", "1.0", "1"]: - factor_expr = ModelParser.parse_expression(factor) - factor_expr.update_scope(neuron.get_scope()) - factor_expr.accept(ASTSymbolTableVisitor()) - assignment_str += "(" + self._printer_no_origin.print(factor_expr) + ") * " - - if "_is_post_port" in dir(inport) and inport._is_post_port: - orig_port_name = inport[:inport.index("__for_")] - buffer_type = neuron.paired_synapse.get_scope().resolve_to_symbol(orig_port_name, SymbolKind.VARIABLE).get_type_symbol() - else: - buffer_type = neuron.get_scope().resolve_to_symbol(inport.get_name(), SymbolKind.VARIABLE).get_type_symbol() - - assignment_str += str(inport) - if not buffer_type.print_nestml_type() in ["1.", "1.0", "1"]: - assignment_str += " / (" + buffer_type.print_nestml_type() + ")" - ast_assignment = ModelParser.parse_assignment(assignment_str) - ast_assignment.update_scope(neuron.get_scope()) - ast_assignment.accept(ASTSymbolTableVisitor()) - - inport_name = inport.get_name() - if inport.has_vector_parameter(): - inport_name += "_" + str(ASTUtils.get_numeric_vector_size(inport)) - if not inport_name in spike_updates.keys(): - spike_updates[inport_name] = [] - - spike_updates[inport_name].append(ast_assignment) - - return spike_updates, post_spike_updates diff --git a/pynestml/codegeneration/nest_compartmental_code_generator.py b/pynestml/codegeneration/nest_compartmental_code_generator.py index 4711bc497..81f785e00 100644 --- a/pynestml/codegeneration/nest_compartmental_code_generator.py +++ b/pynestml/codegeneration/nest_compartmental_code_generator.py @@ -18,14 +18,18 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -import shutil + from typing import Any, Dict, List, Mapping, Optional import datetime import os from jinja2 import TemplateRuntimeError + +from odetoolbox import analysis + import pynestml +from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.codegeneration.code_generator import CodeGenerator from pynestml.codegeneration.nest_assignments_helper import NestAssignmentsHelper from pynestml.codegeneration.nest_declarations_helper import NestDeclarationsHelper @@ -47,15 +51,14 @@ from pynestml.meta_model.ast_assignment import ASTAssignment from pynestml.meta_model.ast_block_with_variables import ASTBlockWithVariables from pynestml.meta_model.ast_input_port import ASTInputPort -from pynestml.meta_model.ast_kernel import ASTKernel from pynestml.meta_model.ast_model import ASTModel from pynestml.meta_model.ast_node_factory import ASTNodeFactory from pynestml.meta_model.ast_variable import ASTVariable from pynestml.symbol_table.symbol_table import SymbolTable from pynestml.symbols.symbol import SymbolKind +from pynestml.transformers.inline_expression_expansion_transformer import InlineExpressionExpansionTransformer from pynestml.utils.ast_vector_parameter_setter_and_printer import ASTVectorParameterSetterAndPrinter from pynestml.utils.ast_vector_parameter_setter_and_printer_factory import ASTVectorParameterSetterAndPrinterFactory -from pynestml.transformers.inline_expression_expansion_transformer import InlineExpressionExpansionTransformer from pynestml.utils.mechanism_processing import MechanismProcessing from pynestml.utils.channel_processing import ChannelProcessing from pynestml.utils.concentration_processing import ConcentrationProcessing @@ -72,7 +75,6 @@ from pynestml.utils.synapse_processing import SynapseProcessing from pynestml.visitors.ast_random_number_generator_visitor import ASTRandomNumberGeneratorVisitor from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor -from odetoolbox import analysis class NESTCompartmentalCodeGenerator(CodeGenerator): @@ -132,10 +134,6 @@ def __init__(self, options: Optional[Mapping[str, Any]] = None): self.setup_printers() - # maps kernel names to their analytic solutions separately - # this is needed for the cm_syns case - self.kernel_name_to_analytic_solver = {} - def setup_printers(self): self._constant_printer = ConstantPrinter() @@ -283,27 +281,20 @@ def analyse_transform_neurons(self, neurons: List[ASTModel]) -> None: code, message = Messages.get_analysing_transforming_model( neuron.get_name()) Logger.log_message(None, code, message, None, LoggingLevel.INFO) - spike_updates = self.analyse_neuron(neuron) - neuron.spike_updates = spike_updates + self.analyse_neuron(neuron) def create_ode_indict(self, neuron: ASTModel, - parameters_block: ASTBlockWithVariables, - kernel_buffers: Mapping[ASTKernel, - ASTInputPort]): - odetoolbox_indict = self.transform_ode_and_kernels_to_json( - neuron, parameters_block, kernel_buffers) + parameters_block: ASTBlockWithVariables): + odetoolbox_indict = self.transform_odes_to_json(neuron, parameters_block) odetoolbox_indict["options"] = {} odetoolbox_indict["options"]["output_timestep_symbol"] = "__h" return odetoolbox_indict def ode_solve_analytically(self, neuron: ASTModel, - parameters_block: ASTBlockWithVariables, - kernel_buffers: Mapping[ASTKernel, - ASTInputPort]): - odetoolbox_indict = self.create_ode_indict( - neuron, parameters_block, kernel_buffers) + parameters_block: ASTBlockWithVariables): + odetoolbox_indict = self.create_ode_indict(neuron, parameters_block) full_solver_result = analysis( odetoolbox_indict, @@ -322,8 +313,7 @@ def ode_solve_analytically(self, return full_solver_result, analytic_solver - def ode_toolbox_analysis(self, neuron: ASTModel, - kernel_buffers: Mapping[ASTKernel, ASTInputPort]): + def ode_toolbox_analysis(self, neuron: ASTModel): """ Prepare data for ODE-toolbox input format, invoke ODE-toolbox analysis via its API, and return the output. """ @@ -332,15 +322,13 @@ def ode_toolbox_analysis(self, neuron: ASTModel, equations_block = neuron.get_equations_blocks()[0] - if len(equations_block.get_kernels()) == 0 and len( - equations_block.get_ode_equations()) == 0: + if len(equations_block.get_ode_equations()) == 0: # no equations defined -> no changes to the neuron return None, None parameters_block = neuron.get_parameters_blocks()[0] - solver_result, analytic_solver = self.ode_solve_analytically( - neuron, parameters_block, kernel_buffers) + solver_result, analytic_solver = self.ode_solve_analytically(neuron, parameters_block) # if numeric solver is required, generate a stepping function that # includes each state variable @@ -349,8 +337,7 @@ def ode_toolbox_analysis(self, neuron: ASTModel, x for x in solver_result if x["solver"].startswith("numeric")] if numeric_solvers: - odetoolbox_indict = self.create_ode_indict( - neuron, parameters_block, kernel_buffers) + odetoolbox_indict = self.create_ode_indict(neuron, parameters_block) solver_result = analysis( odetoolbox_indict, disable_stiffness_check=True, @@ -388,13 +375,6 @@ def find_non_equations_state_variables(self, neuron: ASTModel): used_in_eq = True break - # check for any state variables being used by a kernel - for kern in neuron.get_equations_blocks()[0].get_kernels(): - for kern_var in kern.get_variables(): - if kern_var.get_name() == var.get_name(): - used_in_eq = True - break - # if no usage found at this point, we have a non-equation state # variable if not used_in_eq: @@ -423,26 +403,6 @@ def analyse_neuron(self, neuron: ASTModel) -> List[ASTAssignment]: self.non_equations_state_variables[neuron.get_name()].extend( ASTUtils.all_variables_defined_in_block(neuron.get_state_blocks()[0])) - return [] - - # goes through all convolve() inside ode's from equations block - # if they have delta kernels, use sympy to expand the expression, then - # find the convolve calls and replace them with constant value 1 - # then return every subexpression that had that convolve() replaced - delta_factors = ASTUtils.get_delta_factors_(neuron, equations_block) - - # goes through all convolve() inside equations block - # extracts what kernel is paired with what spike buffer - # returns pairs (kernel, spike_buffer) - kernel_buffers = ASTUtils.generate_kernel_buffers( - neuron, equations_block) - - # replace convolve(g_E, spikes_exc) with g_E__X__spikes_exc[__d] - # done by searching for every ASTSimpleExpression inside equations_block - # which is a convolve call and substituting that call with - # newly created ASTVariable kernel__X__spike_buffer - ASTUtils.replace_convolve_calls_with_buffers_(neuron, equations_block) - # substitute inline expressions with each other # such that no inline expression references another inline expression; # deference inline_expressions inside ode_equations @@ -454,16 +414,7 @@ def analyse_neuron(self, neuron: ASTModel) -> List[ASTAssignment]: # "update_expressions" key in those solvers contains a mapping # {expression1: update_expression1, expression2: update_expression2} - analytic_solver, numeric_solver = self.ode_toolbox_analysis( - neuron, kernel_buffers) - - """ - # separate analytic solutions by kernel - # this is is needed for the synaptic case - self.kernel_name_to_analytic_solver[neuron.get_name( - )] = self.ode_toolbox_anaysis_cm_syns(neuron, kernel_buffers) - """ - + analytic_solver, numeric_solver = self.ode_toolbox_analysis(neuron) self.analytic_solver[neuron.get_name()] = analytic_solver self.numeric_solver[neuron.get_name()] = numeric_solver @@ -471,17 +422,6 @@ def analyse_neuron(self, neuron: ASTModel) -> List[ASTAssignment]: self.non_equations_state_variables[neuron.get_name()] = \ self.find_non_equations_state_variables(neuron) - # gather all variables used by kernels and delete their declarations - # they will be inserted later again, but this time with values redefined - # by odetoolbox, higher order variables don't get deleted here - ASTUtils.remove_initial_values_for_kernels(neuron) - - # delete all kernels as they are all converted into buffers - # and corresponding update formulas calculated by odetoolbox - # Remember them in a variable though - kernels = ASTUtils.remove_kernel_definitions_from_equations_block( - neuron) - # Every ODE variable (a variable of order > 0) is renamed according to ODE-toolbox conventions # their initial values are replaced by expressions suggested by ODE-toolbox. # Differential order can now be set to 0, becase they can directly represent the value of the derivative now. @@ -495,22 +435,11 @@ def analyse_neuron(self, neuron: ASTModel) -> List[ASTAssignment]: # corresponding updates ASTUtils.remove_ode_definitions_from_equations_block(neuron) - # restore state variables that were referenced by kernels - # and set their initial values by those suggested by ODE-toolbox - ASTUtils.create_initial_values_for_kernels( - neuron, [analytic_solver, numeric_solver], kernels) - # Inside all remaining expressions, translate all remaining variable names # according to the naming conventions of ODE-toolbox. ASTUtils.replace_variable_names_in_expressions( neuron, [analytic_solver, numeric_solver]) - # find all inline kernels defined as ASTSimpleExpression - # that have a single kernel convolution aliasing variable ('__X__') - # translate all remaining variable names according to the naming - # conventions of ODE-toolbox - ASTUtils.replace_convolution_aliasing_inlines(neuron) - # add variable __h to internals block ASTUtils.add_timestep_symbol(neuron) @@ -520,10 +449,10 @@ def analyse_neuron(self, neuron: ASTModel) -> List[ASTAssignment]: neuron, self.analytic_solver[neuron.get_name()]["propagators"]) # generate how to calculate the next spike update - self.update_symbol_table(neuron, kernel_buffers) + self.update_symbol_table(neuron) # find any spike update expressions defined by the user spike_updates = self.get_spike_update_expressions( - neuron, kernel_buffers, [analytic_solver, numeric_solver], delta_factors) + neuron, [analytic_solver, numeric_solver]) return spike_updates @@ -682,20 +611,16 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: expr_ast.accept(ASTSymbolTableVisitor()) namespace["numeric_update_expressions"][sym] = expr_ast - namespace["spike_updates"] = neuron.spike_updates - namespace["recordable_state_variables"] = [ sym for sym in neuron.get_state_symbols() if namespace["declarations"].get_domain_from_type( - sym.get_type_symbol()) == "double" and sym.is_recordable and not ASTUtils.is_delta_kernel( - neuron.get_kernel_by_name( - sym.name))] + sym.get_type_symbol()) == "double" and sym.is_recordable] namespace["recordable_inline_expressions"] = [ sym for sym in neuron.get_inline_expression_symbols() if namespace["declarations"].get_domain_from_type( sym.get_type_symbol()) == "double" and sym.is_recordable] # parameter symbols with initial values namespace["parameter_syms_with_iv"] = [sym for sym in neuron.get_parameter_symbols( - ) if sym.has_declaring_expression() and (not neuron.get_kernel_by_name(sym.name))] + ) if sym.has_declaring_expression()] namespace["cm_unique_suffix"] = self.getUniqueSuffix(neuron) # get the mechanisms info dictionaries and enrich them. @@ -734,14 +659,14 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: return namespace - def update_symbol_table(self, neuron, kernel_buffers): + def update_symbol_table(self, neuron): """ Update symbol table and scope. """ SymbolTable.delete_model_scope(neuron.get_name()) symbol_table_visitor = ASTSymbolTableVisitor() - symbol_table_visitor.after_ast_rewrite_ = True neuron.accept(symbol_table_visitor) + CoCosManager.check_cocos(neuron, after_ast_rewrite=True) SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope()) def _get_ast_variable(self, neuron, var_name) -> Optional[ASTVariable]: @@ -755,7 +680,7 @@ def _get_ast_variable(self, neuron, var_name) -> Optional[ASTVariable]: return None def create_initial_values_for_ode_toolbox_odes( - self, neuron, solver_dicts, kernel_buffers, kernels): + self, neuron, solver_dicts): """ Add the variables used in ODEs from the ode-toolbox result dictionary as ODEs in NESTML AST. """ @@ -776,101 +701,17 @@ def create_initial_values_for_ode_toolbox_odes( # here, overwrite is allowed because initial values might be # repeated between numeric and analytic solver - if ASTUtils.variable_in_kernels(var_name, kernels): - expr = "0" # for kernels, "initial value" returned by ode-toolbox is actually the increment value; the actual initial value is assumed to be 0 - if not ASTUtils.declaration_in_state_block(neuron, var_name): ASTUtils.add_declaration_to_state_block( neuron, var_name, expr) - def get_spike_update_expressions( + def transform_odes_to_json( self, neuron: ASTModel, - kernel_buffers, - solver_dicts, - delta_factors) -> List[ASTAssignment]: - """ - Generate the equations that update the dynamical variables when incoming spikes arrive. To be invoked after ode-toolbox. - - For example, a resulting `assignment_str` could be "I_kernel_in += (in_spikes/nS) * 1". The values are taken from the initial values for each corresponding dynamical variable, either from ode-toolbox or directly from user specification in the model. - - Note that for kernels, `initial_values` actually contains the increment upon spike arrival, rather than the initial value of the corresponding ODE dimension. - - XXX: TODO: update this function signature (+ templates) to match NESTCodegenerator::get_spike_update_expressions(). - - - """ - spike_updates = [] - - for kernel, spike_input_port in kernel_buffers: - if neuron.get_scope().resolve_to_symbol( - str(spike_input_port), SymbolKind.VARIABLE) is None: - continue - - buffer_type = neuron.get_scope().resolve_to_symbol( - str(spike_input_port), SymbolKind.VARIABLE).get_type_symbol() - - if ASTUtils.is_delta_kernel(kernel): - continue - - for kernel_var in kernel.get_variables(): - for var_order in range( - ASTUtils.get_kernel_var_order_from_ode_toolbox_result( - kernel_var.get_name(), solver_dicts)): - kernel_spike_buf_name = ASTUtils.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_input_port, var_order) - expr = ASTUtils.get_initial_value_from_ode_toolbox_result( - kernel_spike_buf_name, solver_dicts) - assert expr is not None, "Initial value not found for kernel " + kernel_var - expr = str(expr) - if expr in ["0", "0.", "0.0"]: - continue # skip adding the statement if we're only adding zero - - assignment_str = kernel_spike_buf_name + " += " - assignment_str += "(" + str(spike_input_port) + ")" - if expr not in ["1.", "1.0", "1"]: - assignment_str += " * (" + expr + ")" - - if not buffer_type.print_nestml_type() in ["1.", "1.0", "1"]: - assignment_str += " / (" + buffer_type.print_nestml_type() + ")" - - ast_assignment = ModelParser.parse_assignment( - assignment_str) - ast_assignment.update_scope(neuron.get_scope()) - ast_assignment.accept(ASTSymbolTableVisitor()) - - spike_updates.append(ast_assignment) - - for k, factor in delta_factors.items(): - var = k[0] - inport = k[1] - assignment_str = var.get_name() + "'" * (var.get_differential_order() - 1) + " += " - if factor not in ["1.", "1.0", "1"]: - assignment_str += "(" + self._printer.print(ModelParser.parse_expression(factor)) + ") * " - assignment_str += str(inport) - ast_assignment = ModelParser.parse_assignment(assignment_str) - ast_assignment.update_scope(neuron.get_scope()) - ast_assignment.accept(ASTSymbolTableVisitor()) - - spike_updates.append(ast_assignment) - - return spike_updates - - def transform_ode_and_kernels_to_json( - self, - neuron: ASTModel, - parameters_block, - kernel_buffers): + parameters_block): """ Converts AST node to a JSON representation suitable for passing to ode-toolbox. - Each kernel has to be generated for each spike buffer convolve in which it occurs, e.g. if the NESTML model code contains the statements - - convolve(G, ex_spikes) - convolve(G, in_spikes) - - then `kernel_buffers` will contain the pairs `(G, ex_spikes)` and `(G, in_spikes)`, from which two ODEs will be generated, with dynamical state (variable) names `G__X__ex_spikes` and `G__X__in_spikes`. - :param parameters_block: ASTBlockWithVariables :return: Dict """ @@ -879,8 +720,7 @@ def transform_ode_and_kernels_to_json( equations_block = neuron.get_equations_blocks()[0] for equation in equations_block.get_ode_equations(): - # n.b. includes single quotation marks to indicate differential - # order + # n.b. includes single quotation marks to indicate differential order lhs = ASTUtils.to_ode_toolbox_name( equation.get_lhs().get_complete_name()) rhs = self._ode_toolbox_printer.print(equation.get_rhs()) @@ -900,43 +740,6 @@ def transform_ode_and_kernels_to_json( iv_symbol_name)] = expr odetoolbox_indict["dynamics"].append(entry) - # write a copy for each (kernel, spike buffer) combination - for kernel, spike_input_port in kernel_buffers: - - if ASTUtils.is_delta_kernel(kernel): - # delta function -- skip passing this to ode-toolbox - continue - - for kernel_var in kernel.get_variables(): - expr = ASTUtils.get_expr_from_kernel_var( - kernel, kernel_var.get_complete_name()) - kernel_order = kernel_var.get_differential_order() - kernel_X_spike_buf_name_ticks = ASTUtils.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_input_port, kernel_order, diff_order_symbol="'") - - ASTUtils.replace_rhs_variables(expr, kernel_buffers) - - entry = {} - entry["expression"] = kernel_X_spike_buf_name_ticks + " = " + str(expr) - - # initial values need to be declared for order 1 up to kernel - # order (e.g. none for kernel function f(t) = ...; 1 for kernel - # ODE f'(t) = ...; 2 for f''(t) = ... and so on) - entry["initial_values"] = {} - for order in range(kernel_order): - iv_sym_name_ode_toolbox = ASTUtils.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_input_port, order, diff_order_symbol="'") - symbol_name_ = kernel_var.get_name() + "'" * order - symbol = equations_block.get_scope().resolve_to_symbol( - symbol_name_, SymbolKind.VARIABLE) - assert symbol is not None, "Could not find initial value for variable " + symbol_name_ - initial_value_expr = symbol.get_declaring_expression() - assert initial_value_expr is not None, "No initial value found for variable name " + symbol_name_ - entry["initial_values"][iv_sym_name_ode_toolbox] = self._ode_toolbox_printer.print( - initial_value_expr) - - odetoolbox_indict["dynamics"].append(entry) - odetoolbox_indict["parameters"] = {} if parameters_block is not None: for decl in parameters_block.get_declarations(): diff --git a/pynestml/codegeneration/printers/cpp_variable_printer.py b/pynestml/codegeneration/printers/cpp_variable_printer.py index 2a6af847a..f8d0eca3c 100644 --- a/pynestml/codegeneration/printers/cpp_variable_printer.py +++ b/pynestml/codegeneration/printers/cpp_variable_printer.py @@ -34,9 +34,9 @@ def _print_cpp_name(cls, variable_name: str) -> str: :param variable_name: a single name. :return: a string representation """ - differential_order = variable_name.count("\"") + differential_order = variable_name.count("'") if differential_order > 0: - return variable_name.replace("\"", "").replace("$", "__DOLLAR") + "__" + "d" * differential_order + return variable_name.replace("'", "").replace("$", "__DOLLAR") + "__" + "d" * differential_order return variable_name.replace("$", "__DOLLAR") diff --git a/pynestml/codegeneration/printers/nest_variable_printer.py b/pynestml/codegeneration/printers/nest_variable_printer.py index 3a64da9a7..d481d552e 100644 --- a/pynestml/codegeneration/printers/nest_variable_printer.py +++ b/pynestml/codegeneration/printers/nest_variable_printer.py @@ -20,11 +20,9 @@ # along with NEST. If not, see . from __future__ import annotations -from typing import Dict, Optional from typing import Dict, Optional - from pynestml.codegeneration.nest_code_generator_utils import NESTCodeGeneratorUtils from pynestml.codegeneration.printers.cpp_variable_printer import CppVariablePrinter from pynestml.codegeneration.printers.expression_printer import ExpressionPrinter diff --git a/pynestml/codegeneration/printers/nestml_expression_printer.py b/pynestml/codegeneration/printers/nestml_expression_printer.py new file mode 100644 index 000000000..ac8b62209 --- /dev/null +++ b/pynestml/codegeneration/printers/nestml_expression_printer.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# +# nestml_expression_printer.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from pynestml.codegeneration.printers.expression_printer import ExpressionPrinter +from pynestml.meta_model.ast_arithmetic_operator import ASTArithmeticOperator +from pynestml.meta_model.ast_comparison_operator import ASTComparisonOperator +from pynestml.meta_model.ast_expression import ASTExpression +from pynestml.meta_model.ast_logical_operator import ASTLogicalOperator +from pynestml.meta_model.ast_node import ASTNode +from pynestml.meta_model.ast_unary_operator import ASTUnaryOperator + + +class NESTMLExpressionPrinter(ExpressionPrinter): + r""" + Printer for ``ASTExpression`` nodes in NESTML syntax. + """ + + def print(self, node: ASTNode) -> str: + if isinstance(node, ASTExpression): + if node.get_implicit_conversion_factor() and not node.get_implicit_conversion_factor() == 1: + return "(" + str(node.get_implicit_conversion_factor()) + " * (" + self.print_expression(node) + "))" + + return self.print_expression(node) + + if isinstance(node, ASTArithmeticOperator): + return self.print_arithmetic_operator(node) + + if isinstance(node, ASTUnaryOperator): + return self.print_unary_operator(node) + + if isinstance(node, ASTComparisonOperator): + return self.print_comparison_operator(node) + + if isinstance(node, ASTLogicalOperator): + return self.print_logical_operator(node) + + return self._simple_expression_printer.print(node) + + def print_logical_operator(self, node: ASTLogicalOperator) -> str: + if node.is_logical_and: + return " and " + + if node.is_logical_or: + return " or " + + raise Exception("Unknown logical operator") + + def print_comparison_operator(self, node: ASTComparisonOperator) -> str: + if node.is_lt: + return " < " + + if node.is_le: + return " <= " + + if node.is_eq: + return " == " + + if node.is_ne: + return " != " + + if node.is_ne2: + return " <> " + + if node.is_ge: + return " >= " + + if node.is_gt: + return " > " + + raise RuntimeError("(PyNestML.ComparisonOperator.Print) Type of comparison operator not specified!") + + def print_unary_operator(self, node: ASTUnaryOperator) -> str: + if node.is_unary_plus: + return "+" + + if node.is_unary_minus: + return "-" + + if node.is_unary_tilde: + return "~" + + raise RuntimeError("Type of unary operator not specified!") + + def print_arithmetic_operator(self, node: ASTArithmeticOperator) -> str: + if node.is_times_op: + return " * " + + if node.is_div_op: + return " / " + + if node.is_modulo_op: + return " % " + + if node.is_plus_op: + return " + " + + if node.is_minus_op: + return " - " + + if node.is_pow_op: + return " ** " + + raise RuntimeError("(PyNestML.ArithmeticOperator.Print) Arithmetic operator not specified.") + + def print_expression(self, node: ASTExpression) -> str: + ret = "" + if node.is_expression(): + if node.is_encapsulated: + ret += "(" + if node.is_logical_not: + ret += "not " + if node.is_unary_operator(): + ret += self.print(node.get_unary_operator()) + ret += self.print(node.get_expression()) + if node.is_encapsulated: + ret += ")" + elif node.is_compound_expression(): + ret += self.print(node.get_lhs()) + ret += self.print(node.get_binary_operator()) + ret += self.print(node.get_rhs()) + elif node.is_ternary_operator(): + ret += self.print(node.get_condition()) + "?" + self.print( + node.get_if_true()) + ":" + self.print(node.get_if_not()) + + return ret diff --git a/pynestml/codegeneration/printers/nestml_function_call_printer.py b/pynestml/codegeneration/printers/nestml_function_call_printer.py new file mode 100644 index 000000000..21efa320d --- /dev/null +++ b/pynestml/codegeneration/printers/nestml_function_call_printer.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# nestml_function_call_printer.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from pynestml.codegeneration.printers.function_call_printer import FunctionCallPrinter +from pynestml.meta_model.ast_function_call import ASTFunctionCall + + +class NESTMLFunctionCallPrinter(FunctionCallPrinter): + r""" + Printer for ASTFunctionCall in C++ syntax. + """ + + def print_function_call(self, node: ASTFunctionCall) -> str: + ret = str(node.get_name()) + "(" + for i in range(0, len(node.get_args())): + ret += self._expression_printer.print(node.get_args()[i]) + if i < len(node.get_args()) - 1: # in the case that it is not the last arg, print also a comma + ret += "," + + ret += ")" + + return ret diff --git a/pynestml/codegeneration/printers/nestml_printer.py b/pynestml/codegeneration/printers/nestml_printer.py index f03d9931d..48545eec3 100644 --- a/pynestml/codegeneration/printers/nestml_printer.py +++ b/pynestml/codegeneration/printers/nestml_printer.py @@ -19,7 +19,17 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . +from typing import Optional, Union + +from pynestml.codegeneration.printers import nest_variable_printer +from pynestml.codegeneration.printers.constant_printer import ConstantPrinter from pynestml.codegeneration.printers.model_printer import ModelPrinter +from pynestml.codegeneration.printers.nest_variable_printer import NESTVariablePrinter +from pynestml.codegeneration.printers.nestml_expression_printer import NESTMLExpressionPrinter +from pynestml.codegeneration.printers.nestml_function_call_printer import NESTMLFunctionCallPrinter +from pynestml.codegeneration.printers.nestml_simple_expression_printer import NESTMLSimpleExpressionPrinter +from pynestml.codegeneration.printers.nestml_variable_printer import NESTMLVariablePrinter +from pynestml.codegeneration.printers.variable_printer import VariablePrinter from pynestml.meta_model.ast_arithmetic_operator import ASTArithmeticOperator from pynestml.meta_model.ast_assignment import ASTAssignment from pynestml.meta_model.ast_bit_operator import ASTBitOperator @@ -38,21 +48,21 @@ from pynestml.meta_model.ast_function_call import ASTFunctionCall from pynestml.meta_model.ast_if_clause import ASTIfClause from pynestml.meta_model.ast_if_stmt import ASTIfStmt +from pynestml.meta_model.ast_inline_expression import ASTInlineExpression from pynestml.meta_model.ast_input_block import ASTInputBlock from pynestml.meta_model.ast_input_port import ASTInputPort from pynestml.meta_model.ast_input_qualifier import ASTInputQualifier from pynestml.meta_model.ast_kernel import ASTKernel from pynestml.meta_model.ast_logical_operator import ASTLogicalOperator +from pynestml.meta_model.ast_model import ASTModel +from pynestml.meta_model.ast_model_body import ASTModelBody from pynestml.meta_model.ast_namespace_decorator import ASTNamespaceDecorator from pynestml.meta_model.ast_nestml_compilation_unit import ASTNestMLCompilationUnit -from pynestml.meta_model.ast_model_body import ASTModelBody from pynestml.meta_model.ast_ode_equation import ASTOdeEquation -from pynestml.meta_model.ast_inline_expression import ASTInlineExpression -from pynestml.meta_model.ast_model import ASTModel from pynestml.meta_model.ast_on_condition_block import ASTOnConditionBlock from pynestml.meta_model.ast_output_block import ASTOutputBlock -from pynestml.meta_model.ast_parameter import ASTParameter from pynestml.meta_model.ast_on_receive_block import ASTOnReceiveBlock +from pynestml.meta_model.ast_parameter import ASTParameter from pynestml.meta_model.ast_return_stmt import ASTReturnStmt from pynestml.meta_model.ast_simple_expression import ASTSimpleExpression from pynestml.meta_model.ast_small_stmt import ASTSmallStmt @@ -74,6 +84,13 @@ class NESTMLPrinter(ModelPrinter): def __init__(self): self.indent = 0 + self._expression_printer = NESTMLExpressionPrinter(simple_expression_printer=None) + self._constant_printer = ConstantPrinter() + self._function_call_printer = NESTMLFunctionCallPrinter(expression_printer=self._expression_printer) + self._variable_printer = NESTMLVariablePrinter(expression_printer=self._expression_printer) + self._simple_expression_printer = NESTMLSimpleExpressionPrinter(variable_printer=self._variable_printer, function_call_printer=self._function_call_printer, constant_printer=self._constant_printer) + self._expression_printer._simple_expression_printer = self._simple_expression_printer + def print_model(self, node: ASTModel) -> str: ret = print_ml_comments(node.pre_comments, self.indent, False) self.inc_indent() @@ -82,26 +99,32 @@ def print_model(self, node: ASTModel) -> str: self.dec_indent() return ret - def print_arithmetic_operator(celf, node: ASTArithmeticOperator) -> str: - if node.is_times_op: - return " * " + def print_constant(self, const: Union[str, float, int]) -> str: + return self._constant_printer.print_constant(const) + + def print_function_call(self, node: ASTFunctionCall) -> str: + return self._function_call_printer.print_function_call(node) - if node.is_div_op: - return " / " + def print_variable(self, node: ASTVariable) -> str: + return self._variable_printer.print_variable(node) - if node.is_modulo_op: - return " % " + def print_simple_expression(self, node: ASTSimpleExpression) -> str: + return self._simple_expression_printer.print_simple_expression(node) - if node.is_plus_op: - return " + " + def print_expression(self, node: ASTExpression) -> str: + return self._expression_printer.print_expression(node) - if node.is_minus_op: - return " - " + def print_arithmetic_operator(self, node: ASTArithmeticOperator) -> str: + return self._expression_printer.print_arithmetic_operator(node) - if node.is_pow_op: - return " ** " + def print_unary_operator(self, node: ASTUnaryOperator) -> str: + return self._expression_printer.print_unary_operator(node) - raise RuntimeError("(PyNestML.ArithmeticOperator.Print) Arithmetic operator not specified.") + def print_comparison_operator(self, node: ASTComparisonOperator) -> str: + return self._expression_printer.print_comparison_operator(node) + + def print_logical_operator(self, node: ASTLogicalOperator) -> str: + return self._expression_printer.print_logical_operator(node) def print_assignment(self, node: ASTAssignment) -> str: ret = print_ml_comments(node.pre_comments, self.indent, False) @@ -173,30 +196,6 @@ def print_model_body(self, node: ASTModelBody) -> str: ret += self.print(elem) return ret - def print_comparison_operator(self, node: ASTComparisonOperator) -> str: - if node.is_lt: - return " < " - - if node.is_le: - return " <= " - - if node.is_eq: - return " == " - - if node.is_ne: - return " != " - - if node.is_ne2: - return " <> " - - if node.is_ge: - return " >= " - - if node.is_gt: - return " > " - - raise RuntimeError("(PyNestML.ComparisonOperator.Print) Type of comparison operator not specified!") - def print_compound_stmt(self, node: ASTCompoundStmt) -> str: if node.is_if_stmt(): return self.print(node.get_if_stmt()) @@ -274,27 +273,6 @@ def print_equations_block(self, node: ASTEquationsBlock) -> str: self.dec_indent() return ret - def print_expression(self, node: ASTExpression) -> str: - ret = "" - if node.is_expression(): - if node.is_encapsulated: - ret += "(" - if node.is_logical_not: - ret += "not " - if node.is_unary_operator(): - ret += self.print(node.get_unary_operator()) - ret += self.print(node.get_expression()) - if node.is_encapsulated: - ret += ")" - elif node.is_compound_expression(): - ret += self.print(node.get_lhs()) - ret += self.print(node.get_binary_operator()) - ret += self.print(node.get_rhs()) - elif node.is_ternary_operator(): - ret += self.print(node.get_condition()) + "?" + self.print( - node.get_if_true()) + ":" + self.print(node.get_if_not()) - return ret - def print_for_stmt(self, node: ASTForStmt) -> str: ret = print_ml_comments(node.pre_comments, self.indent, False) ret += print_n_spaces(self.indent) @@ -304,6 +282,9 @@ def print_for_stmt(self, node: ASTForStmt) -> str: ret += self.print(node.get_block()) return ret + def print_function_call(self, node: ASTFunctionCall) -> str: + return self._function_call_printer.print_function_call(node) + def print_function(self, node: ASTFunction) -> str: ret = print_ml_comments(node.pre_comments, self.indent) ret += "function " + node.get_name() + "(" @@ -317,15 +298,6 @@ def print_function(self, node: ASTFunction) -> str: ret += self.print(node.get_block()) + "\n" return ret - def print_function_call(self, node: ASTFunctionCall) -> str: - ret = str(node.get_name()) + "(" - for i in range(0, len(node.get_args())): - ret += self.print(node.get_args()[i]) - if i < len(node.get_args()) - 1: # in the case that it is not the last arg, print also a comma - ret += "," - ret += ")" - return ret - def print_if_clause(self, node: ASTIfClause) -> str: ret = print_ml_comments(node.pre_comments, self.indent) ret += print_n_spaces(self.indent) + "if " + self.print(node.get_condition()) + ":" @@ -379,15 +351,6 @@ def print_input_qualifier(self, node: ASTInputQualifier) -> str: return "excitatory" return "" - def print_logical_operator(self, node: ASTLogicalOperator) -> str: - if node.is_logical_and: - return " and " - - if node.is_logical_or: - return " or " - - raise Exception("Unknown logical operator") - def print_compilation_unit(self, node: ASTNestMLCompilationUnit) -> str: ret = "" if node.get_model_list() is not None: @@ -443,33 +406,6 @@ def print_return_stmt(self, node: ASTReturnStmt): ret += "return " + (self.print(node.get_expression()) if node.has_expression() else "") return ret - def print_simple_expression(self, node: ASTSimpleExpression) -> str: - if node.is_function_call(): - return self.print(node.function_call) - - if node.is_boolean_true: - return "true" - - if node.is_boolean_false: - return "false" - - if node.is_inf_literal: - return "inf" - - if node.is_numeric_literal(): - if node.variable is not None: - return str(node.numeric_literal) + self.print(node.variable) - - return str(node.numeric_literal) - - if node.is_variable(): - return self.print_variable(node.get_variable()) - - if node.is_string(): - return node.get_string() - - raise RuntimeError("Simple rhs at %s not specified!" % str(node.get_source_position())) - def print_small_stmt(self, node: ASTSmallStmt) -> str: if node.is_assignment(): ret = self.print(node.get_assignment()) @@ -491,18 +427,6 @@ def print_stmt(self, node: ASTStmt): return self.print(node.compound_stmt) - def print_unary_operator(self, node: ASTUnaryOperator) -> str: - if node.is_unary_plus: - return "+" - - if node.is_unary_minus: - return "-" - - if node.is_unary_tilde: - return "~" - - raise RuntimeError("Type of unary operator not specified!") - def print_unit_type(self, node: ASTUnitType) -> str: if node.is_encapsulated: return "(" + self.print(node.compound_unit) + ")" @@ -538,17 +462,6 @@ def print_update_block(self, node: ASTUpdateBlock): ret += self.print(node.get_block()) return ret - def print_variable(self, node: ASTVariable): - ret = node.name - - if node.get_vector_parameter(): - ret += "[" + self.print(node.get_vector_parameter()) + "]" - - for i in range(1, node.differential_order + 1): - ret += "'" - - return ret - def print_while_stmt(self, node: ASTWhileStmt) -> str: ret = print_ml_comments(node.pre_comments, self.indent, False) ret += (print_n_spaces(self.indent) + "while " + self.print(node.get_condition()) diff --git a/pynestml/codegeneration/printers/nestml_simple_expression_printer.py b/pynestml/codegeneration/printers/nestml_simple_expression_printer.py new file mode 100644 index 000000000..8198c239a --- /dev/null +++ b/pynestml/codegeneration/printers/nestml_simple_expression_printer.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# nestml_simple_expression_printer.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from pynestml.codegeneration.printers.simple_expression_printer import SimpleExpressionPrinter +from pynestml.meta_model.ast_function_call import ASTFunctionCall +from pynestml.meta_model.ast_node import ASTNode +from pynestml.meta_model.ast_simple_expression import ASTSimpleExpression +from pynestml.meta_model.ast_variable import ASTVariable + + +class NESTMLSimpleExpressionPrinter(SimpleExpressionPrinter): + r""" + Printer for ASTSimpleExpressions in NESTML syntax. + """ + + def _print(self, node: ASTNode) -> str: + if isinstance(node, ASTVariable): + return self._variable_printer.print(node) + + if isinstance(node, ASTFunctionCall): + return self._function_call_printer.print(node) + + return self.print_simple_expression(node) + + def print(self, node: ASTNode) -> str: + if node.get_implicit_conversion_factor() and not node.get_implicit_conversion_factor() == 1: + return "(" + str(node.get_implicit_conversion_factor()) + " * (" + self._print(node) + "))" + + return self._print(node) + + def print_simple_expression(self, node: ASTSimpleExpression) -> str: + if node.is_function_call(): + return self.print(node.function_call) + + if node.is_boolean_true: + return "true" + + if node.is_boolean_false: + return "false" + + if node.is_inf_literal: + return "inf" + + if node.is_numeric_literal(): + if node.variable is not None: + # numeric literal + physical unit + return str(node.numeric_literal) + self.print(node.variable) + + return str(node.numeric_literal) + + if node.is_variable(): + return self._variable_printer.print_variable(node.get_variable()) + + if node.is_string(): + return node.get_string() + + raise RuntimeError("Simple rhs at %s not specified!" % str(node.get_source_position())) diff --git a/pynestml/codegeneration/printers/nestml_simple_expression_printer_units_as_factors.py b/pynestml/codegeneration/printers/nestml_simple_expression_printer_units_as_factors.py new file mode 100644 index 000000000..ce9710585 --- /dev/null +++ b/pynestml/codegeneration/printers/nestml_simple_expression_printer_units_as_factors.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# nestml_simple_expression_printer_units_as_factors.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from pynestml.codegeneration.printers.nestml_simple_expression_printer import NESTMLSimpleExpressionPrinter +from pynestml.meta_model.ast_simple_expression import ASTSimpleExpression + + +class NESTMLSimpleExpressionPrinterUnitsAsFactors(NESTMLSimpleExpressionPrinter): + r""" + Same as the NESTMLPrinter, except print unit literals with a multiplication operator between (for example "42 * ms" instead of "42 ms"). + """ + + def print_simple_expression(self, node: ASTSimpleExpression) -> str: + if node.is_numeric_literal(): + if node.variable is not None: + # numeric literal + physical unit + return str(node.numeric_literal) + " * " + self.print(node.variable) + + return str(node.numeric_literal) + + return super().print_simple_expression(node) diff --git a/pynestml/codegeneration/printers/nestml_variable_printer.py b/pynestml/codegeneration/printers/nestml_variable_printer.py index 0e7ea9741..834df2918 100644 --- a/pynestml/codegeneration/printers/nestml_variable_printer.py +++ b/pynestml/codegeneration/printers/nestml_variable_printer.py @@ -23,15 +23,20 @@ from pynestml.meta_model.ast_variable import ASTVariable -class NestMLVariablePrinter(VariablePrinter): +class NESTMLVariablePrinter(VariablePrinter): r""" Print ``ASTVariable``s in NESTML syntax. """ - def print_variable(self, node: ASTVariable) -> str: - """ - Print a variable node - :param node: the node to print - :return: string representation - """ - return node.get_complete_name() + def print_variable(self, node: ASTVariable): + assert isinstance(node, ASTVariable) + + ret = node.name + + if node.get_vector_parameter(): + ret += "[" + self._expression_printer.print(node.get_vector_parameter()) + "]" + + for i in range(1, node.differential_order + 1): + ret += "'" + + return ret diff --git a/pynestml/codegeneration/printers/ode_toolbox_expression_printer.py b/pynestml/codegeneration/printers/ode_toolbox_expression_printer.py index af8acd4b8..09a8a918d 100644 --- a/pynestml/codegeneration/printers/ode_toolbox_expression_printer.py +++ b/pynestml/codegeneration/printers/ode_toolbox_expression_printer.py @@ -26,7 +26,7 @@ class ODEToolboxExpressionPrinter(CppExpressionPrinter): r""" - Printer for ``ASTExpression`` nodes in ODE-toolbox syntax. + Printer for ``ASTExpression`` nodes in ODE-toolbox (sympy) syntax. """ def _print_ternary_operator_expression(self, node: ASTExpression) -> str: diff --git a/pynestml/codegeneration/printers/ode_toolbox_variable_printer.py b/pynestml/codegeneration/printers/ode_toolbox_variable_printer.py index 734eb530d..4b6a9b952 100644 --- a/pynestml/codegeneration/printers/ode_toolbox_variable_printer.py +++ b/pynestml/codegeneration/printers/ode_toolbox_variable_printer.py @@ -29,9 +29,9 @@ class ODEToolboxVariablePrinter(VariablePrinter): """ def print_variable(self, node: ASTVariable) -> str: - """ + r""" Print variable. :param node: the node to print :return: string representation """ - return node.get_complete_name().replace("$", "__DOLLAR") + return node.get_name().replace("$", "__DOLLAR") + "__d" * node.get_differential_order() diff --git a/pynestml/codegeneration/python_standalone_code_generator.py b/pynestml/codegeneration/python_standalone_code_generator.py index f44123743..d6afaa095 100644 --- a/pynestml/codegeneration/python_standalone_code_generator.py +++ b/pynestml/codegeneration/python_standalone_code_generator.py @@ -111,7 +111,6 @@ def setup_printers(self): # GSL printers self._gsl_variable_printer = PythonSteppingFunctionVariablePrinter(None) - print("In Python code generator: created self._gsl_variable_printer = " + str(self._gsl_variable_printer)) self._gsl_function_call_printer = PythonSteppingFunctionFunctionCallPrinter(None) self._gsl_printer = PythonExpressionPrinter(simple_expression_printer=PythonSimpleExpressionPrinter(variable_printer=self._gsl_variable_printer, constant_printer=self._constant_printer, diff --git a/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronClass.jinja2 b/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronClass.jinja2 index e59ecbff0..29c3acfaf 100644 --- a/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronClass.jinja2 +++ b/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronClass.jinja2 @@ -278,10 +278,8 @@ std::vector< std::tuple< int, int > > {{ neuronName }}::rport_to_nestml_buffer_i // copy state struct S_ {%- for init in neuron.get_state_symbols() %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(init.name)) %} {%- set node = utils.get_state_variable_by_name(astnode, init.get_symbol_name()) %} {{ nest_codegen_utils.print_symbol_origin(init, node) % printer_no_origin.print(node) }} = __n.{{ nest_codegen_utils.print_symbol_origin(init, node) % printer_no_origin.print(node) }}; -{%- endif %} {%- endfor %} // copy internals V_ @@ -752,32 +750,10 @@ void {{ neuronName }}::update(nest::Time const & origin, const long from, const update_delay_variables(); {%- endif %} - - /** - * subthreshold updates of the convolution variables - * - * step 1: regardless of whether and how integrate_odes() will be called, update variables due to convolutions - **/ -{% if uses_analytic_solver %} -{%- for variable_name in analytic_state_variables: %} -{%- if "__X__" in variable_name %} -{%- set update_expr = update_expressions[variable_name] %} -{%- set var_ast = utils.get_variable_by_name(astnode, variable_name)%} -{%- set var_symbol = var_ast.get_scope().resolve_to_symbol(variable_name, SymbolKind.VARIABLE)%} -{%- if use_gap_junctions %} - const {{ type_symbol_printer.print(var_symbol.type_symbol) }} {{variable_name}}__tmp_ = {{ printer.print(update_expr) | replace("B_." + gap_junction_port + "_grid_sum_", "(B_." + gap_junction_port + "_grid_sum_ + __I_gap)") }}; -{%- else %} - const {{ type_symbol_printer.print(var_symbol.type_symbol) }} {{variable_name}}__tmp_ = {{ printer.print(update_expr) }}; -{%- endif %} -{%- endif %} -{%- endfor %} -{%- endif %} - - +{% if neuron.get_update_blocks() %} /** * Begin NESTML generated code for the update block(s) **/ -{% if neuron.get_update_blocks() %} {%- filter indent(2) %} {%- for block in neuron.get_update_blocks() %} {%- set ast = block.get_block() %} @@ -803,22 +779,6 @@ void {{ neuronName }}::update(nest::Time const & origin, const long from, const } {%- endfor %} - /** - * subthreshold updates of the convolution variables - * - * step 2: regardless of whether and how integrate_odes() was called, update variables due to convolutions. Set to the updated values at the end of the timestep. - **/ -{% if uses_analytic_solver %} -{%- for variable_name in analytic_state_variables: %} -{%- if "__X__" in variable_name %} -{%- set update_expr = update_expressions[variable_name] %} -{%- set var_ast = utils.get_variable_by_name(astnode, variable_name)%} -{%- set var_symbol = var_ast.get_scope().resolve_to_symbol(variable_name, SymbolKind.VARIABLE)%} - {{ printer.print(var_ast) }} = {{variable_name}}__tmp_; -{%- endif %} -{%- endfor %} -{%- endif %} - {%- if purely_numeric_state_variables_moved %} /** @@ -837,26 +797,23 @@ void {{ neuronName }}::update(nest::Time const & origin, const long from, const {%- include "directives_cpp/PredefinedFunction_integrate_odes.jinja2" %} {%- endif %} - /** - * spike updates due to convolutions - **/ -{% filter indent(4) %} -{%- include "directives_cpp/ApplySpikesFromBuffers.jinja2" %} -{%- endfilter %} - /** * Begin NESTML generated code for the onCondition block(s) **/ {% if neuron.get_on_condition_blocks() %} {%- for block in neuron.get_on_condition_blocks() %} + /** + * Begin NESTML generated code for the ``onCondition({{ nestml_printer.print(block.get_cond_expr()) }})`` block + **/ + if ({{ printer.print(block.get_cond_expr()) }}) { {%- set ast = block.get_block() %} {%- if ast.print_comment('*') | length > 1 %} -/* - {{ast.print_comment('*')}} - */ -{%- endif %} + /** + * {{ ast.print_comment('*') }} + **/ +{% endif %} {%- filter indent(6) %} {%- include "directives_cpp/Block.jinja2" %} {%- endfilter %} @@ -1283,14 +1240,6 @@ void {%- endfor %} {%- endfilter %} - /** - * updates due to convolutions - **/ - -{%- for _, spike_update in post_spike_updates.items() %} - {{ printer.print(utils.get_variable_by_name(astnode, spike_update.get_variable().get_complete_name())) }} += 1.; -{%- endfor %} - /** * push back history **/ diff --git a/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronHeader.jinja2 b/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronHeader.jinja2 index 41e2d988a..7fd50d2bb 100644 --- a/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronHeader.jinja2 +++ b/pynestml/codegeneration/resources_nest/point_neuron/common/NeuronHeader.jinja2 @@ -191,18 +191,15 @@ public: double t_; //!< point in time when spike occurred (in ms) {%- for var in (purely_numeric_state_variables_moved + analytic_state_variables_moved) | sort %} double {{ var }}_; +{%- endfor %} -{%- set inline = utils.get_inline_expression_by_constructed_rhs_name(paired_synapse_original_model, var) %} -{%- if inline and utils.inline_aliases_convolution(inline) %} - double get_{{ inline.get_variable_name() + "__for_" + paired_synapse_original_model.get_name() }}() const // getter for an inline expression -{%- else %} +{%- for var in (purely_numeric_state_variables_moved + analytic_state_variables_moved) | sort %} double get_{{ var }}() const -{%- endif %} { return {{ var }}_; } - {%- endfor %} + size_t access_counter_; //!< access counter to enable removal of the entry, once all neurons read it }; {%- if state_vars_that_need_continuous_buffering | length > 0 %} @@ -390,10 +387,8 @@ public: {% filter indent(2, True) -%} {%- for variable_symbol in neuron.get_state_symbols() %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(variable_symbol.name)) %} -{%- set variable = utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} -{%- include "directives_cpp/MemberVariableGetterSetter.jinja2" %} -{% endif %} +{%- set variable = utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} +{%- include "directives_cpp/MemberVariableGetterSetter.jinja2" %} {% endfor %} {%- endfilter %} {%- endif %} @@ -1015,14 +1010,12 @@ inline void {{neuronName}}::get_status(DictionaryDatum &__d) const {%- endfilter %} {%- endfor %} - // initial values for state variables in ODE or kernel + // initial values for state variables in ODEs {%- for variable_symbol in neuron.get_state_symbols() %} {%- set variable = utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(variable_symbol.name)) %} {%- filter indent(2) %} {%- include "directives_cpp/WriteInDictionary.jinja2" %} {%- endfilter %} -{%- endif -%} {%- endfor %} {{neuron_parent_class}}::get_status( __d ); @@ -1070,14 +1063,12 @@ inline void {{neuronName}}::set_status(const DictionaryDatum &__d) {%- endfilter %} {%- endfor %} - // initial values for state variables in ODE or kernel + // initial values for state variables in ODEs {%- for variable_symbol in neuron.get_state_symbols() %} {%- set variable = utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(variable_symbol.name)) %} -{%- filter indent(2) %} -{%- include "directives_cpp/ReadFromDictionaryToTmp.jinja2" %} -{%- endfilter %} -{%- endif %} +{%- filter indent(2) %} +{%- include "directives_cpp/ReadFromDictionaryToTmp.jinja2" %} +{%- endfilter %} {%- endfor %} // We now know that (ptmp, stmp) are consistent. We do not @@ -1096,11 +1087,9 @@ inline void {{neuronName}}::set_status(const DictionaryDatum &__d) {%- for variable_symbol in neuron.get_state_symbols() -%} {%- set variable = utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(variable_symbol.name)) %} -{%- filter indent(2) %} -{%- include "directives_cpp/AssignTmpDictionaryValue.jinja2" %} -{%- endfilter %} -{%- endif %} +{%- filter indent(2) %} +{%- include "directives_cpp/AssignTmpDictionaryValue.jinja2" %} +{%- endfilter %} {%- endfor %} {% for invariant in neuron.get_parameter_invariants() %} diff --git a/pynestml/codegeneration/resources_nest/point_neuron/common/SynapseHeader.h.jinja2 b/pynestml/codegeneration/resources_nest/point_neuron/common/SynapseHeader.h.jinja2 index 69af77d21..ebb202632 100644 --- a/pynestml/codegeneration/resources_nest/point_neuron/common/SynapseHeader.h.jinja2 +++ b/pynestml/codegeneration/resources_nest/point_neuron/common/SynapseHeader.h.jinja2 @@ -893,17 +893,6 @@ void get_entry_from_continuous_variable_history(double t, {%- endfilter %} } - /** - * update all convolutions with pre spikes - **/ - -{%- for spike_updates_for_port in spike_updates.values() %} -{%- for spike_update in spike_updates_for_port %} - {{ printer.print(spike_update.get_variable()) }} += 1.; // XXX: TODO: increment with initial value instead of 1 -{%- endfor %} -{%- endfor %} - - /** * in case pre and post spike time coincide and pre update takes priority **/ @@ -1109,7 +1098,7 @@ void {%- for variable_symbol in synapse.get_state_symbols() + synapse.get_parameter_symbols() %} {%- set isHomogeneous = PyNestMLLexer["DECORATOR_HOMOGENEOUS"] in variable_symbol.get_decorators() %} {%- set variable = utils.get_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} -{%- if not isHomogeneous and not is_delta_kernel(synapse.get_kernel_by_name(variable_symbol.name)) and not variable_symbol.is_inline_expression %} +{%- if not isHomogeneous and not variable_symbol.is_inline_expression %} {%- if variable.get_name() == nest_codegen_opt_delay_variable %} // special treatment of NEST delay double tmp_{{ nest_codegen_opt_delay_variable }} = get_delay(); @@ -1145,7 +1134,7 @@ if (__d->known(nest::names::weight)) {%- for variable_symbol in synapse.get_state_symbols() + synapse.get_parameter_symbols() %} {%- set variable = utils.get_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} {%- set isHomogeneous = PyNestMLLexer["DECORATOR_HOMOGENEOUS"] in variable_symbol.get_decorators() %} -{%- if not isHomogeneous and not is_delta_kernel(synapse.get_kernel_by_name(variable_symbol.name)) %} +{%- if not isHomogeneous %} {%- if variable.get_name() == nest_codegen_opt_delay_variable %} // special treatment of NEST delay set_delay(tmp_{{ nest_codegen_opt_delay_variable }}); diff --git a/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/ApplySpikesFromBuffers.jinja2 b/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/ApplySpikesFromBuffers.jinja2 deleted file mode 100644 index 881257451..000000000 --- a/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/ApplySpikesFromBuffers.jinja2 +++ /dev/null @@ -1,6 +0,0 @@ -{% if tracing %}/* generated by {{self._TemplateReference__context.name}} */ {% endif %} -{%- for spike_updates_for_port in spike_updates.values() %} -{%- for ast in spike_updates_for_port -%} -{%- include "directives_cpp/Assignment.jinja2" %} -{%- endfor %} -{%- endfor %} diff --git a/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/PredefinedFunction_integrate_odes.jinja2 b/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/PredefinedFunction_integrate_odes.jinja2 index b630f329a..3f6a1890f 100644 --- a/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/PredefinedFunction_integrate_odes.jinja2 +++ b/pynestml/codegeneration/resources_nest/point_neuron/directives_cpp/PredefinedFunction_integrate_odes.jinja2 @@ -3,8 +3,9 @@ @param ast ASTFunctionCall #} {%- if tracing %}/* generated by {{self._TemplateReference__context.name}} */ {% endif %} - -// start rendered code for integrate_odes({{ ", ".join(utils.integrate_odes_args_strs_from_function_call(ast)) }}) +{ + // start rendered code for integrate_odes({{ ", ".join(utils.integrate_odes_args_strs_from_function_call(ast)) }}) + // this is inside its own block because it declares some temporary variables for the analytic solver {%- if uses_analytic_solver %} {% set analytic_state_variables_ = analytic_state_variables.copy() %} @@ -18,8 +19,10 @@ {%- endif %} {%- if analytic_state_variables_ | length > 0 %} -// analytic solver: integrating state variables (first step): {% for variable_name in analytic_state_variables_ %}{{ variable_name }}, {% endfor %} -{%- include "directives_cpp/AnalyticIntegrationStep_begin.jinja2" %} + // analytic solver: integrating state variables (first step): {% for variable_name in analytic_state_variables_ %}{{ variable_name }}, {% endfor %} +{%- filter indent(4, True) -%} +{%- include "directives_cpp/AnalyticIntegrationStep_begin.jinja2" %} +{%- endfilter %} {%- endif %} {%- endif %} @@ -30,35 +33,17 @@ {%- set numeric_state_variables_to_be_integrated = utils.filter_variables_list(numeric_state_variables_to_be_integrated, ast.get_args()) %} {%- endif %} {%- if numeric_state_variables_to_be_integrated | length > 0 %} -// numeric solver: integrating state variables: {% for variable_name in numeric_state_variables_to_be_integrated %}{{ variable_name }}, {% endfor %} - -{%- if analytic_state_variables_from_convolutions | length > 0 %} -// solver step should update state of convolutions internally, but not change ode_state[] pertaining to convolutions; convolution integration should be independent of integrate_odes() calls -// buffer the old values -{%- for variable_name in analytic_state_variables_from_convolutions %} -{%- set update_expr = update_expressions[variable_name] %} -{%- set variable_symbol = variable_symbols[variable_name] %} -const double {{ variable_name }}__orig = {{ printer.print(utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name())) }}; -{%- endfor %} -{%- endif %} - -{%- include "directives_cpp/GSLIntegrationStep.jinja2" %} - -{%- if analytic_state_variables_from_convolutions | length > 0 %} -// restore the old values for convolutions -{%- for variable_name in analytic_state_variables_from_convolutions %} -{%- set variable_symbol = variable_symbols[variable_name] %} -{{ printer.print(utils.get_state_variable_by_name(astnode, variable_symbol.get_symbol_name())) }} = {{ variable_name }}__orig; -{%- endfor %} -{% endif %} - + // numeric solver: integrating state variables: {% for variable_name in numeric_state_variables_to_be_integrated %}{{ variable_name }}, {% endfor %} +{%- filter indent(4, True) -%} +{%- include "directives_cpp/GSLIntegrationStep.jinja2" %} +{%- endfilter %} {%- endif %} {%- endif %} -{%- if uses_analytic_solver %} -{%- if analytic_state_variables_ | length > 0 %} -// analytic solver: integrating state variables (second step): {% for variable_name in analytic_state_variables_ %}{{ variable_name }}, {% endfor %} - -{%- include "directives_cpp/AnalyticIntegrationStep_end.jinja2" %} -{%- endif %} +{%- if uses_analytic_solver and analytic_state_variables_ | length > 0 %} + // analytic solver: integrating state variables (second step): {% for variable_name in analytic_state_variables_ %}{{ variable_name }}, {% endfor %} +{%- filter indent(4, True) -%} +{%- include "directives_cpp/AnalyticIntegrationStep_end.jinja2" %} +{%- endfilter %} {%- endif %} +} \ No newline at end of file diff --git a/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 b/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 index 5fc3ae589..87c892608 100644 --- a/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 +++ b/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 @@ -191,6 +191,7 @@ class Neuron_{{neuronName}}(Neuron): {%- endif %} {%- endfor %} {%- endfilter %} + pass else: # internals V_ {%- filter indent(6) %} @@ -220,10 +221,8 @@ class Neuron_{{neuronName}}(Neuron): # ------------------------------------------------------------------------- {% filter indent(2, True) -%} {%- for variable_symbol in neuron.get_state_symbols() %} -{%- if not is_delta_kernel(neuron.get_kernel_by_name(variable_symbol.get_symbol_name())) %} {%- set variable = utils.get_variable_by_name(astnode, variable_symbol.get_symbol_name()) %} {%- include "directives_py/MemberVariableGetterSetter.jinja2" %} -{%- endif %} {%- endfor %} {%- endfilter %} @@ -264,13 +263,6 @@ class Neuron_{{neuronName}}(Neuron): {%- set analytic_state_variables_ = utils.filter_variables_list(analytic_state_variables_, ast.get_args()) %} {%- endif %} -{#- always integrate convolutions in time #} -{%- for var in analytic_state_variables %} -{%- if "__X__" in var %} -{%- set tmp = analytic_state_variables_.append(var) %} -{%- endif %} -{%- endfor %} - {%- include "directives_py/AnalyticIntegrationStep_begin.jinja2" %} {%- if uses_numeric_solver %} @@ -285,14 +277,6 @@ class Neuron_{{neuronName}}(Neuron): def step(self, origin: float, timestep: float) -> None: __resolution: float = timestep # do not remove, this is necessary for the resolution() function - # ------------------------------------------------------------------------- - # integrate variables related to convolutions - # ------------------------------------------------------------------------- - -{%- with analytic_state_variables_ = analytic_state_variables_from_convolutions %} -{%- include "directives_py/AnalyticIntegrationStep_begin.jinja2" %} -{%- endwith %} - # ------------------------------------------------------------------------- # NESTML generated code for the update block # ------------------------------------------------------------------------- @@ -306,21 +290,6 @@ class Neuron_{{neuronName}}(Neuron): {%- endfilter %} {%- endif %} - # ------------------------------------------------------------------------- - # integrate variables related to convolutions - # ------------------------------------------------------------------------- - -{%- with analytic_state_variables_ = analytic_state_variables_from_convolutions %} -{%- include "directives_py/AnalyticIntegrationStep_end.jinja2" %} -{%- endwith %} - - # ------------------------------------------------------------------------- - # process spikes from buffers - # ------------------------------------------------------------------------- -{%- filter indent(4, True) -%} -{%- include "directives_py/ApplySpikesFromBuffers.jinja2" %} -{%- endfilter %} - # ------------------------------------------------------------------------- # begin NESTML generated code for the onReceive block(s) # ------------------------------------------------------------------------- diff --git a/pynestml/codegeneration/resources_python_standalone/point_neuron/directives_py/ApplySpikesFromBuffers.jinja2 b/pynestml/codegeneration/resources_python_standalone/point_neuron/directives_py/ApplySpikesFromBuffers.jinja2 deleted file mode 100644 index c0952b2f5..000000000 --- a/pynestml/codegeneration/resources_python_standalone/point_neuron/directives_py/ApplySpikesFromBuffers.jinja2 +++ /dev/null @@ -1,6 +0,0 @@ -{%- if tracing %}# generated by {{self._TemplateReference__context.name}}{% endif %} -{%- for spike_updates_for_port in spike_updates.values() %} -{%- for ast in spike_updates_for_port -%} -{%- include "directives_py/Assignment.jinja2" %} -{%- endfor %} -{%- endfor %} diff --git a/pynestml/codegeneration/resources_spinnaker/@NEURON_NAME@_chain_example.py.jinja2 b/pynestml/codegeneration/resources_spinnaker/@NEURON_NAME@_chain_example.py.jinja2 index 0a6974dbb..727c84522 100644 --- a/pynestml/codegeneration/resources_spinnaker/@NEURON_NAME@_chain_example.py.jinja2 +++ b/pynestml/codegeneration/resources_spinnaker/@NEURON_NAME@_chain_example.py.jinja2 @@ -19,7 +19,7 @@ You should have received a copy of the GNU General Public License along with NEST. If not, see . #} -#TODO: Move to spinnaker test +#TODO: Move to spinnaker test # import spynnaker and plotting stuff import pyNN.spiNNaker as p @@ -32,7 +32,7 @@ from python_models8.neuron.builds.{{neuronName}} import {{neuronName}} #TODO: Set names for exitatory input, membrane potential and synaptic response exc_input = "exc_spikes" membranePot = "V_m" -synapticRsp = "I_kernel_exc__X__exc_spikes" +synapticRsp = "I_kernel_exc__conv__exc_spikes" diff --git a/pynestml/codegeneration/spinnaker_code_generator.py b/pynestml/codegeneration/spinnaker_code_generator.py index 2a8fed7de..672d2ba66 100644 --- a/pynestml/codegeneration/spinnaker_code_generator.py +++ b/pynestml/codegeneration/spinnaker_code_generator.py @@ -23,6 +23,7 @@ import copy import os +from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.codegeneration.code_generator import CodeGenerator from pynestml.codegeneration.nest_code_generator import NESTCodeGenerator @@ -137,7 +138,6 @@ def setup_printers(self): # GSL printers self._gsl_variable_printer = PythonSteppingFunctionVariablePrinter(None) - print("In Python code generator: created self._gsl_variable_printer = " + str(self._gsl_variable_printer)) self._gsl_function_call_printer = PythonSteppingFunctionFunctionCallPrinter(None) self._gsl_printer = PythonExpressionPrinter(simple_expression_printer=SpinnakerPythonSimpleExpressionPrinter( variable_printer=self._gsl_variable_printer, @@ -216,6 +216,7 @@ def generate_code(self, models: Sequence[ASTModel]) -> None: for model in models: cloned_model = model.clone() cloned_model.accept(ASTSymbolTableVisitor()) + CoCosManager.check_cocos(cloned_model) cloned_models.append(cloned_model) self.codegen_cpp.generate_code(cloned_models) @@ -224,6 +225,7 @@ def generate_code(self, models: Sequence[ASTModel]) -> None: for model in models: cloned_model = model.clone() cloned_model.accept(ASTSymbolTableVisitor()) + CoCosManager.check_cocos(cloned_model) cloned_models.append(cloned_model) self.codegen_py.generate_code(cloned_models) diff --git a/pynestml/frontend/frontend_configuration.py b/pynestml/frontend/frontend_configuration.py index 173534c95..aae1fc29a 100644 --- a/pynestml/frontend/frontend_configuration.py +++ b/pynestml/frontend/frontend_configuration.py @@ -244,8 +244,8 @@ def handle_module_name(cls, module_name): @classmethod def handle_target_platform(cls, target_platform: Optional[str]): - if target_platform is None or target_platform.upper() == 'NONE': - target_platform = '' # make sure `target_platform` is always a string + if target_platform is None: + target_platform = "NONE" # make sure `target_platform` is always a string from pynestml.frontend.pynestml_frontend import get_known_targets diff --git a/pynestml/frontend/pynestml_frontend.py b/pynestml/frontend/pynestml_frontend.py index c257822de..3a7d0932e 100644 --- a/pynestml/frontend/pynestml_frontend.py +++ b/pynestml/frontend/pynestml_frontend.py @@ -37,10 +37,14 @@ from pynestml.symbols.predefined_types import PredefinedTypes from pynestml.symbols.predefined_units import PredefinedUnits from pynestml.symbols.predefined_variables import PredefinedVariables +from pynestml.transformers.convolutions_transformer import ConvolutionsTransformer +from pynestml.transformers.inline_expression_expansion_transformer import InlineExpressionExpansionTransformer from pynestml.transformers.transformer import Transformer from pynestml.utils.logger import Logger, LoggingLevel from pynestml.utils.messages import Messages from pynestml.utils.model_parser import ModelParser +from pynestml.visitors.ast_parent_visitor import ASTParentVisitor +from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor def get_known_targets(): @@ -59,6 +63,12 @@ def transformers_from_target_name(target_name: str, options: Optional[Mapping[st if options is None: options = {} + # for all targets, add the InlineExpressionExpansionTransformer + transformers.append(InlineExpressionExpansionTransformer()) + + # for all targets, add the convolutions transformer + transformers.append(ConvolutionsTransformer()) + if target_name.upper() in ["NEST", "SPINNAKER"]: from pynestml.transformers.illegal_variable_name_transformer import IllegalVariableNameTransformer @@ -131,10 +141,10 @@ def code_generator_from_target_name(target_name: str, options: Optional[Mapping[ return SpiNNakerCodeGenerator(options) if target_name.upper() == "NONE": - # dummy/null target: user requested to not generate any code + # dummy/null target: user requested to not generate any code (for instance, when just doing validation of a model) code, message = Messages.get_no_code_generated() Logger.log_message(None, code, message, None, LoggingLevel.INFO) - return CodeGenerator("", options) + return CodeGenerator(options) # cannot reach here due to earlier assert -- silence static checker warnings assert "Unknown code generator requested: " + target_name @@ -193,12 +203,17 @@ def generate_target(input_path: Union[str, Sequence[str]], target_platform: str, Enable development mode: code generation is attempted even for models that contain errors, and extra information is rendered in the generated code. codegen_opts : Optional[Mapping[str, Any]] A dictionary containing additional options for the target code generator. + + Return + ------ + errors_occurred + Flag indicating whether errors occurred during processing. False if processing was successful; True if errors occurred in any of the models. """ configure_front_end(input_path, target_platform, target_path, install_path, logging_level, module_name, store_log, suffix, dev, codegen_opts) - if not process() == 0: - raise Exception("Error(s) occurred while processing the model") + + return process() def configure_front_end(input_path: Union[str, Sequence[str]], target_platform: str, target_path=None, @@ -373,34 +388,36 @@ def generate_nest_compartmental_target(input_path: Union[str, Sequence[str]], ta def main() -> int: - """ + r""" Entry point for the command-line application. Returns ------- - The process exit code: 0 for success, > 0 for failure + exit_code + The process exit code: 0 for success, > 0 for failure """ try: FrontendConfiguration.parse_config(sys.argv[1:]) except InvalidPathException as e: print(e) + return 1 + # the default Python recursion limit is 1000, which might not be enough in practice when running an AST visitor on a deep tree, e.g. containing an automatically generated expression sys.setrecursionlimit(10000) + # after all argument have been collected, start the actual processing return int(process()) -def get_parsed_models(): +def get_parsed_models() -> List[ASTModel]: r""" Handle the parsing and validation of the NESTML files Returns ------- - models: Sequence[ASTModel] + models List of correctly parsed models - errors_occurred : bool - Flag indicating whether errors occurred during processing """ # init log dir create_report_dir() @@ -417,36 +434,29 @@ def get_parsed_models(): for nestml_file in nestml_files: parsed_unit = ModelParser.parse_file(nestml_file) - if parsed_unit is None: - # Parsing error in the NESTML model, return True - return [], True + if parsed_unit: + compilation_units.append(parsed_unit) - compilation_units.append(parsed_unit) + # generate a list of all models + models: Sequence[ASTModel] = [] + for compilation_unit in compilation_units: + CoCosManager.check_model_names_unique(compilation_unit) + models.extend(compilation_unit.get_model_list()) - if len(compilation_units) > 0: - # generate a list of all models - models: Sequence[ASTModel] = [] - for compilationUnit in compilation_units: - models.extend(compilationUnit.get_model_list()) + # check that no models with duplicate names have been defined + CoCosManager.check_no_duplicate_compilation_unit_names(models) - # check that no models with duplicate names have been defined - CoCosManager.check_no_duplicate_compilation_unit_names(models) + for model in models: + model.accept(ASTParentVisitor()) + model.accept(ASTSymbolTableVisitor()) - # now exclude those which are broken, i.e. have errors. - for model in models: - if Logger.has_errors(model): - code, message = Messages.get_model_contains_errors(model.get_name()) - Logger.log_message(node=model, code=code, message=message, - error_position=model.get_source_position(), - log_level=LoggingLevel.WARNING) - return [model], True - - return models, False + return models def transform_models(transformers, models): for transformer in transformers: models = transformer.transform(models) + return models @@ -454,14 +464,14 @@ def generate_code(code_generators, models): code_generators.generate_code(models) -def process(): +def process() -> bool: r""" The main toolchain workflow entry point. For all models: parse, validate, transform, generate code and build. - Returns - ------- - errors_occurred : bool - Flag indicating whether errors occurred during processing + Return + ------ + errors_occurred + Flag indicating whether errors occurred during processing. False if processing was successful; True if errors occurred in any of the models. """ # initialize and set options for transformers, code generator and builder @@ -478,20 +488,41 @@ def process(): if len(codegen_and_builder_opts) > 0: raise CodeGeneratorOptionsException("The code generator option(s) \"" + ", ".join(codegen_and_builder_opts.keys()) + "\" do not exist.") - models, errors_occurred = get_parsed_models() + models = get_parsed_models() + + # validation + excluded_models = [] + for model in models: + # only check cocos for models that do not have errors already + if not Logger.has_errors(model.name): + CoCosManager.check_cocos(model) + + # exclude models that have errors + if Logger.has_errors(model.name): + code, message = Messages.get_model_contains_errors(model.get_name()) + Logger.log_message(node=model, code=code, message=message, + error_position=model.get_source_position(), + log_level=LoggingLevel.WARNING) + excluded_models.append(model) + + # exclude models that have errors + models = list(set(models) - set(excluded_models)) + + # transformation(s) + models = transform_models(transformers, models) - if not errors_occurred: - models = transform_models(transformers, models) - generate_code(code_generator, models) + # generate code + generate_code(code_generator, models) - # perform build - if _builder is not None: - _builder.build() + # perform build + if _builder is not None: + _builder.build() if FrontendConfiguration.store_log: store_log_to_file() - return errors_occurred + # return a boolean indicating whether errors occurred + return len(Logger.get_all_messages_of_level(LoggingLevel.ERROR)) > 0 def init_predefined(): diff --git a/pynestml/meta_model/ast_equations_block.py b/pynestml/meta_model/ast_equations_block.py index 18d576138..1174e6633 100644 --- a/pynestml/meta_model/ast_equations_block.py +++ b/pynestml/meta_model/ast_equations_block.py @@ -19,7 +19,8 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from typing import Any, List, Sequence +from typing import Any, List, Optional, Sequence +from pynestml.meta_model.ast_declaration import ASTDeclaration from pynestml.meta_model.ast_inline_expression import ASTInlineExpression from pynestml.meta_model.ast_kernel import ASTKernel @@ -32,14 +33,13 @@ class ASTEquationsBlock(ASTNode): This class is used to store an equations block. """ - def __init__(self, declarations, *args, **kwargs): + def __init__(self, declarations: Optional[List[ASTDeclaration]], *args, **kwargs): """ Standard constructor. Parameters for superclass (ASTNode) can be passed through :python:`*args` and :python:`**kwargs`. - :param declarations: a block of definitions. - :type declarations: ast_block + :param declarations: a list of declarations """ assert (declarations is not None and isinstance(declarations, list)), \ '(PyNestML.AST.EquationsBlock) No or wrong type of declarations provided (%s)!' % type(declarations) @@ -49,6 +49,9 @@ def __init__(self, declarations, *args, **kwargs): or isinstance(decl, ASTInlineExpression)), \ '(PyNestML.AST.EquationsBlock) No or wrong type of ode-element provided (%s)' % type(decl) super(ASTEquationsBlock, self).__init__(*args, **kwargs) + if declarations is None: + declarations = [] + self.declarations = declarations def clone(self): @@ -58,7 +61,7 @@ def clone(self): :return: new AST node instance :rtype: ASTEquationsBlock """ - declarations_dup = None + declarations_dup = [] if self.declarations: declarations_dup = [decl.clone() for decl in self.declarations] dup = ASTEquationsBlock(declarations=declarations_dup, diff --git a/pynestml/meta_model/ast_model.py b/pynestml/meta_model/ast_model.py index 834e56897..b9efcab99 100644 --- a/pynestml/meta_model/ast_model.py +++ b/pynestml/meta_model/ast_model.py @@ -322,7 +322,7 @@ def get_multiple_receptors(self) -> List[VariableSymbol]: def get_kernel_by_name(self, kernel_name: str) -> Optional[ASTKernel]: assert type(kernel_name) is str - kernel_name = kernel_name.split("__X__")[0] + kernel_name = kernel_name.split("__conv__")[0] if not self.get_equations_blocks(): return None @@ -459,48 +459,56 @@ def add_to_internals_block(self, declaration: ASTDeclaration, index: int = -1) - Adds the handed over declaration the internals block :param declaration: a single declaration """ - assert len(self.get_internals_blocks()) <= 1, "Only one internals block supported for now" from pynestml.utils.ast_utils import ASTUtils + from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor + from pynestml.visitors.ast_parent_visitor import ASTParentVisitor + + assert len(self.get_internals_blocks()) <= 1, "Only one internals block supported for now" + if not self.get_internals_blocks(): ASTUtils.create_internal_block(self) + n_declarations = len(self.get_internals_blocks()[0].get_declarations()) if n_declarations == 0: index = 0 else: index = 1 + (index % len(self.get_internals_blocks()[0].get_declarations())) + self.get_internals_blocks()[0].get_declarations().insert(index, declaration) declaration.update_scope(self.get_internals_blocks()[0].get_scope()) - from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor - from pynestml.visitors.ast_parent_visitor import ASTParentVisitor symtable_vistor = ASTSymbolTableVisitor() symtable_vistor.block_type_stack.push(BlockType.INTERNALS) - declaration.accept(symtable_vistor) - self.get_internals_blocks()[0].accept(ASTParentVisitor()) + self.accept(ASTParentVisitor()) + self.accept(symtable_vistor) symtable_vistor.block_type_stack.pop() + from pynestml.cocos.co_cos_manager import CoCosManager + CoCosManager.check_cocos(self) def add_to_state_block(self, declaration: ASTDeclaration) -> None: """ Adds the handed over declaration to an arbitrary state block. A state block will be created if none exists. :param declaration: a single declaration. """ - assert len(self.get_state_blocks()) <= 1, "Only one internals block supported for now" + from pynestml.symbols.symbol import SymbolKind from pynestml.utils.ast_utils import ASTUtils + from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor + from pynestml.visitors.ast_parent_visitor import ASTParentVisitor + + assert len(self.get_state_blocks()) <= 1, "Only one internals block supported for now" + if not self.get_state_blocks(): ASTUtils.create_state_block(self) + self.get_state_blocks()[0].get_declarations().append(declaration) declaration.update_scope(self.get_state_blocks()[0].get_scope()) - from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor - from pynestml.visitors.ast_parent_visitor import ASTParentVisitor symtable_vistor = ASTSymbolTableVisitor() symtable_vistor.block_type_stack.push(BlockType.STATE) - declaration.accept(symtable_vistor) - self.get_state_blocks()[0].accept(ASTParentVisitor()) + self.accept(ASTParentVisitor()) + self.accept(symtable_vistor) symtable_vistor.block_type_stack.pop() - from pynestml.symbols.symbol import SymbolKind - assert declaration.get_variables()[0].get_scope().resolve_to_symbol( - declaration.get_variables()[0].get_name(), SymbolKind.VARIABLE) is not None - assert declaration.get_scope().resolve_to_symbol(declaration.get_variables()[0].get_name(), - SymbolKind.VARIABLE) is not None + + assert declaration.get_variables()[0].get_scope().resolve_to_symbol(declaration.get_variables()[0].get_name(), SymbolKind.VARIABLE) is not None + assert declaration.get_scope().resolve_to_symbol(declaration.get_variables()[0].get_name(), SymbolKind.VARIABLE) is not None def print_comment(self, prefix: str = "") -> str: """ @@ -566,7 +574,6 @@ def get_spike_input_port_names(self) -> List[str]: """ Returns a list of all spike input ports defined in the model. """ - print("get_spike_input_port_names = " + str([port.get_symbol_name() for port in self.get_spike_input_ports()])) return [port.get_symbol_name() for port in self.get_spike_input_ports()] def get_continuous_input_ports(self) -> List[VariableSymbol]: diff --git a/pynestml/symbols/symbol.py b/pynestml/symbols/symbol.py index 1e294566b..c73435c6d 100644 --- a/pynestml/symbols/symbol.py +++ b/pynestml/symbols/symbol.py @@ -18,8 +18,8 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from abc import ABCMeta, abstractmethod +from abc import ABCMeta, abstractmethod from enum import Enum diff --git a/pynestml/symbols/type_symbol.py b/pynestml/symbols/type_symbol.py index 7047cdbca..a3eb28a12 100644 --- a/pynestml/symbols/type_symbol.py +++ b/pynestml/symbols/type_symbol.py @@ -18,11 +18,11 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . + from abc import ABCMeta, abstractmethod from pynestml.symbols.symbol import Symbol from pynestml.utils.logger import Logger, LoggingLevel -from pynestml.utils.messages import Messages class TypeSymbol(Symbol): @@ -198,6 +198,7 @@ def is_castable_to(self, _other_type): def binary_operation_not_defined_error(self, _operator, _other): from pynestml.symbols.error_type_symbol import ErrorTypeSymbol + from pynestml.utils.messages import Messages result = ErrorTypeSymbol() code, message = Messages.get_binary_operation_not_defined( lhs=self.print_nestml_type(), operator=_operator, rhs=_other.print_nestml_type()) @@ -208,6 +209,7 @@ def binary_operation_not_defined_error(self, _operator, _other): def unary_operation_not_defined_error(self, _operator): from pynestml.symbols.error_type_symbol import ErrorTypeSymbol result = ErrorTypeSymbol() + from pynestml.utils.messages import Messages code, message = Messages.get_unary_operation_not_defined(_operator, self.print_symbol()) Logger.log_message(code=code, message=message, error_position=self.referenced_object.get_source_position(), @@ -226,6 +228,7 @@ def inverse_of_unit(cls, other): return result def warn_implicit_cast_from_to(self, _from, _to): + from pynestml.utils.messages import Messages code, message = Messages.get_implicit_cast_rhs_to_lhs(_to.print_symbol(), _from.print_symbol()) Logger.log_message(code=code, message=message, error_position=self.get_referenced_object().get_source_position(), diff --git a/pynestml/symbols/unit_type_symbol.py b/pynestml/symbols/unit_type_symbol.py index 37c43b035..1f9977de0 100644 --- a/pynestml/symbols/unit_type_symbol.py +++ b/pynestml/symbols/unit_type_symbol.py @@ -19,6 +19,7 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . +from typing import Optional from pynestml.symbols.type_symbol import TypeSymbol from pynestml.utils.logger import Logger, LoggingLevel from pynestml.utils.messages import Messages @@ -131,12 +132,12 @@ def __sub__(self, other): def add_or_sub_another_unit(self, other): if self.equals(other): return other - else: - return self.attempt_magnitude_cast(other) + + return self.attempt_magnitude_cast(other) def attempt_magnitude_cast(self, other): if self.differs_only_in_magnitude(other): - factor = UnitTypeSymbol.get_conversion_factor(self.astropy_unit, other.astropy_unit) + factor = UnitTypeSymbol.get_conversion_factor(other.astropy_unit, self.astropy_unit) other.referenced_object.set_implicit_conversion_factor(factor) code, message = Messages.get_implicit_magnitude_conversion(self, other, factor) Logger.log_message(code=code, message=message, @@ -144,18 +145,20 @@ def attempt_magnitude_cast(self, other): log_level=LoggingLevel.INFO) return self - else: - return self.binary_operation_not_defined_error('+/-', other) - # TODO: change order of parameters to conform with the from_to scheme. - # TODO: Also rename to reflect that, i.e. get_conversion_factor_from_to + return self.binary_operation_not_defined_error('+/-', other) + @classmethod - def get_conversion_factor(cls, to, _from): + def get_conversion_factor(cls, _from, to) -> Optional[float]: """ - Calculates the conversion factor from _convertee_unit to target_unit. - Behaviour is only well-defined if both units have the same physical base type + Calculates the conversion factor from _convertee_unit to target_unit. Behaviour is only well-defined if both units have the same physical base type. """ - factor = (_from / to).si.scale + try: + factor = (_from / to).si.scale + except BaseException: + # this can fail in case of e.g. trying to convert from "1/s" to "2/s" + return None + return factor def is_castable_to(self, _other_type): diff --git a/pynestml/transformers/assign_implicit_conversion_factors_transformer.py b/pynestml/transformers/assign_implicit_conversion_factors_transformer.py new file mode 100644 index 000000000..f44ee12d5 --- /dev/null +++ b/pynestml/transformers/assign_implicit_conversion_factors_transformer.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +# +# assign_implicit_conversion_factors_transformer.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from typing import Sequence, Union + +from pynestml.meta_model.ast_compound_stmt import ASTCompoundStmt +from pynestml.meta_model.ast_declaration import ASTDeclaration +from pynestml.meta_model.ast_inline_expression import ASTInlineExpression +from pynestml.meta_model.ast_node import ASTNode +from pynestml.meta_model.ast_small_stmt import ASTSmallStmt +from pynestml.meta_model.ast_stmt import ASTStmt +from pynestml.symbols.error_type_symbol import ErrorTypeSymbol +from pynestml.symbols.predefined_types import PredefinedTypes +from pynestml.symbols.symbol import SymbolKind +from pynestml.symbols.template_type_symbol import TemplateTypeSymbol +from pynestml.symbols.variadic_type_symbol import VariadicTypeSymbol +from pynestml.transformers.transformer import Transformer +from pynestml.utils.ast_source_location import ASTSourceLocation +from pynestml.utils.ast_utils import ASTUtils +from pynestml.utils.logger import LoggingLevel, Logger +from pynestml.utils.logging_helper import LoggingHelper +from pynestml.utils.messages import Messages +from pynestml.utils.type_caster import TypeCaster +from pynestml.visitors.ast_visitor import ASTVisitor + + +class AssignImplicitConversionFactorsTransformer(Transformer): + r""" + Assign implicit conversion factors in expressions. + """ + + def transform(self, models: Union[ASTNode, Sequence[ASTNode]]) -> Union[ASTNode, Sequence[ASTNode]]: + single = False + if isinstance(models, ASTNode): + single = True + models = [models] + + for model in models: + model.accept(AssignImplicitConversionFactorVisitor()) + self.__assign_return_types(model) + + if single: + return models[0] + return models + + def __assign_return_types(self, _node): + for userDefinedFunction in _node.get_functions(): + symbol = userDefinedFunction.get_scope().resolve_to_symbol(userDefinedFunction.get_name(), + SymbolKind.FUNCTION) + # first ensure that the block contains at least one statement + if symbol is not None and len(userDefinedFunction.get_block().get_stmts()) > 0: + # now check that the last statement is a return + self.__check_return_recursively(userDefinedFunction, + symbol.get_return_type(), + userDefinedFunction.get_block().get_stmts(), + False) + # now if it does not have a statement, but uses a return type, it is an error + elif symbol is not None and userDefinedFunction.has_return_type() and \ + not symbol.get_return_type().equals(PredefinedTypes.get_void_type()): + code, message = Messages.get_no_return() + Logger.log_message(node=_node, code=code, message=message, + error_position=userDefinedFunction.get_source_position(), + log_level=LoggingLevel.ERROR) + + def __check_return_recursively(self, processed_function, type_symbol=None, stmts=None, ret_defined: bool = False) -> None: + """ + For a handed over statement, it checks if the statement is a return statement and if it is typed according to the handed over type symbol. + :param type_symbol: a single type symbol + :type type_symbol: type_symbol + :param stmts: a list of statements, either simple or compound + :type stmts: list(ASTSmallStmt,ASTCompoundStmt) + :param ret_defined: indicates whether a ret has already been defined after this block of stmt, thus is not + necessary. Implies that the return has been defined in the higher level block + """ + # in order to ensure that in the sub-blocks, a return is not necessary, we check if the last one in this + # block is a return statement, thus it is not required to have a return in the sub-blocks, but optional + last_statement = stmts[len(stmts) - 1] + ret_defined = False or ret_defined + if (len(stmts) > 0 and isinstance(last_statement, ASTStmt) + and last_statement.is_small_stmt() + and last_statement.small_stmt.is_return_stmt()): + ret_defined = True + + # now check that returns are there if necessary and correctly typed + for c_stmt in stmts: + if c_stmt.is_small_stmt(): + stmt = c_stmt.small_stmt + else: + stmt = c_stmt.compound_stmt + + # if it is a small statement, check if it is a return statement + if isinstance(stmt, ASTSmallStmt) and stmt.is_return_stmt(): + # first check if the return is the last one in this block of statements + if stmts.index(c_stmt) != (len(stmts) - 1): + code, message = Messages.get_not_last_statement('Return') + Logger.log_message(error_position=stmt.get_source_position(), + code=code, message=message, + log_level=LoggingLevel.WARNING) + + # now check that it corresponds to the declared type + if stmt.get_return_stmt().has_expression() and type_symbol is PredefinedTypes.get_void_type(): + code, message = Messages.get_type_different_from_expected(PredefinedTypes.get_void_type(), + stmt.get_return_stmt().get_expression().type) + Logger.log_message(error_position=stmt.get_source_position(), + message=message, code=code, log_level=LoggingLevel.ERROR) + + # if it is not void check if the type corresponds to the one stated + if not stmt.get_return_stmt().has_expression() and \ + not type_symbol.equals(PredefinedTypes.get_void_type()): + code, message = Messages.get_type_different_from_expected(PredefinedTypes.get_void_type(), + type_symbol) + Logger.log_message(error_position=stmt.get_source_position(), + message=message, code=code, log_level=LoggingLevel.ERROR) + + if stmt.get_return_stmt().has_expression(): + type_of_return = stmt.get_return_stmt().get_expression().type + if isinstance(type_of_return, ErrorTypeSymbol): + code, message = Messages.get_type_could_not_be_derived(processed_function.get_name()) + Logger.log_message(error_position=stmt.get_source_position(), + code=code, message=message, log_level=LoggingLevel.ERROR) + elif not type_of_return.equals(type_symbol): + TypeCaster.try_to_recover_or_error(type_symbol, type_of_return, + stmt.get_return_stmt().get_expression()) + elif isinstance(stmt, ASTCompoundStmt): + # otherwise it is a compound stmt, thus check recursively + if stmt.is_if_stmt(): + self.__check_return_recursively(processed_function, + type_symbol, + stmt.get_if_stmt().get_if_clause().get_block().get_stmts(), + ret_defined) + for else_ifs in stmt.get_if_stmt().get_elif_clauses(): + self.__check_return_recursively(processed_function, + type_symbol, else_ifs.get_block().get_stmts(), ret_defined) + if stmt.get_if_stmt().has_else_clause(): + self.__check_return_recursively(processed_function, + type_symbol, + stmt.get_if_stmt().get_else_clause().get_block().get_stmts(), + ret_defined) + elif stmt.is_while_stmt(): + self.__check_return_recursively(processed_function, + type_symbol, stmt.get_while_stmt().get_block().get_stmts(), + ret_defined) + elif stmt.is_for_stmt(): + self.__check_return_recursively(processed_function, + type_symbol, stmt.get_for_stmt().get_block().get_stmts(), + ret_defined) + # now, if a return statement has not been defined in the corresponding higher level block, we have to ensure that it is defined here + elif not ret_defined and stmts.index(c_stmt) == (len(stmts) - 1): + if not (isinstance(stmt, ASTSmallStmt) and stmt.is_return_stmt()): + code, message = Messages.get_no_return() + Logger.log_message(error_position=stmt.get_source_position(), log_level=LoggingLevel.ERROR, + code=code, message=message) + + +class AssignImplicitConversionFactorVisitor(ASTVisitor): + """ + This visitor checks that all expression correspond to the expected type. + """ + + def visit_declaration(self, node): + """ + Visits a single declaration and asserts that type of lhs is equal to type of rhs. + :param node: a single declaration. + :type node: ASTDeclaration + """ + assert isinstance(node, ASTDeclaration) + if node.has_expression(): + if node.get_expression().get_source_position().equals(ASTSourceLocation.get_added_source_position()): + # no type checks are executed for added nodes, since we assume correctness + return + lhs_type = node.get_data_type().get_type_symbol() + rhs_type = node.get_expression().type + if isinstance(rhs_type, ErrorTypeSymbol): + LoggingHelper.drop_missing_type_error(node) + return + if self.__types_do_not_match(lhs_type, rhs_type): + TypeCaster.try_to_recover_or_error(lhs_type, rhs_type, node.get_expression()) + + def visit_inline_expression(self, node): + """ + Visits a single inline expression and asserts that type of lhs is equal to type of rhs. + """ + assert isinstance(node, ASTInlineExpression) + lhs_type = node.get_data_type().get_type_symbol() + rhs_type = node.get_expression().type + if isinstance(rhs_type, ErrorTypeSymbol): + LoggingHelper.drop_missing_type_error(node) + return + + if self.__types_do_not_match(lhs_type, rhs_type): + TypeCaster.try_to_recover_or_error(lhs_type, rhs_type, node.get_expression()) + + def visit_assignment(self, node): + """ + Visits a single expression and assures that type(lhs) == type(rhs). + :param node: a single assignment. + :type node: ASTAssignment + """ + from pynestml.meta_model.ast_assignment import ASTAssignment + assert isinstance(node, ASTAssignment) + + if node.get_source_position().equals(ASTSourceLocation.get_added_source_position()): + # no type checks are executed for added nodes, since we assume correctness + return + if node.is_direct_assignment: # case a = b is simple + self.handle_simple_assignment(node) + else: + self.handle_compound_assignment(node) # e.g. a *= b + + def handle_compound_assignment(self, node): + rhs_expr = node.get_expression() + lhs_variable_symbol = node.get_variable().resolve_in_own_scope() + rhs_type_symbol = rhs_expr.type + + if lhs_variable_symbol is None: + code, message = Messages.get_equation_var_not_in_state_block(node.get_variable().get_complete_name()) + Logger.log_message(code=code, message=message, error_position=node.get_source_position(), + log_level=LoggingLevel.ERROR) + return + + if isinstance(rhs_type_symbol, ErrorTypeSymbol): + LoggingHelper.drop_missing_type_error(node) + return + + lhs_type_symbol = lhs_variable_symbol.get_type_symbol() + + if node.is_compound_product: + if self.__types_do_not_match(lhs_type_symbol, lhs_type_symbol * rhs_type_symbol): + TypeCaster.try_to_recover_or_error(lhs_type_symbol, lhs_type_symbol * rhs_type_symbol, + node.get_expression()) + return + return + + if node.is_compound_quotient: + if self.__types_do_not_match(lhs_type_symbol, lhs_type_symbol / rhs_type_symbol): + TypeCaster.try_to_recover_or_error(lhs_type_symbol, lhs_type_symbol / rhs_type_symbol, + node.get_expression()) + return + return + + assert node.is_compound_sum or node.is_compound_minus + if self.__types_do_not_match(lhs_type_symbol, rhs_type_symbol): + TypeCaster.try_to_recover_or_error(lhs_type_symbol, rhs_type_symbol, + node.get_expression()) + + @staticmethod + def __types_do_not_match(lhs_type_symbol, rhs_type_symbol): + if lhs_type_symbol is None: + return True + + return not lhs_type_symbol.equals(rhs_type_symbol) + + def handle_simple_assignment(self, node): + from pynestml.symbols.symbol import SymbolKind + lhs_variable_symbol = node.get_scope().resolve_to_symbol(node.get_variable().get_complete_name(), + SymbolKind.VARIABLE) + + rhs_type_symbol = node.get_expression().type + if isinstance(rhs_type_symbol, ErrorTypeSymbol): + LoggingHelper.drop_missing_type_error(node) + return + + if lhs_variable_symbol is not None and self.__types_do_not_match(lhs_variable_symbol.get_type_symbol(), + rhs_type_symbol): + TypeCaster.try_to_recover_or_error(lhs_variable_symbol.get_type_symbol(), rhs_type_symbol, + node.get_expression()) + + def visit_function_call(self, node): + """ + Check consistency for a single function call: check if the called function has been declared, whether the number and types of arguments correspond to the declaration, etc. + + :param node: a single function call. + :type node: ASTFunctionCall + """ + func_name = node.get_name() + + if func_name == 'convolve': + return + + symbol = node.get_scope().resolve_to_symbol(node.get_name(), SymbolKind.FUNCTION) + + if symbol is None and ASTUtils.is_function_delay_variable(node): + return + + # first check if the function has been declared + if symbol is None: + code, message = Messages.get_function_not_declared(node.get_name()) + Logger.log_message(error_position=node.get_source_position(), log_level=LoggingLevel.ERROR, + code=code, message=message) + return + + # check if the number of arguments is the same as in the symbol; accept anything for variadic types + is_variadic: bool = len(symbol.get_parameter_types()) == 1 and isinstance(symbol.get_parameter_types()[0], VariadicTypeSymbol) + if (not is_variadic) and len(node.get_args()) != len(symbol.get_parameter_types()): + code, message = Messages.get_wrong_number_of_args(str(node), len(symbol.get_parameter_types()), + len(node.get_args())) + Logger.log_message(code=code, message=message, log_level=LoggingLevel.ERROR, + error_position=node.get_source_position()) + return + + # finally check if the call is correctly typed + expected_types = symbol.get_parameter_types() + actual_args = node.get_args() + actual_types = [arg.type for arg in actual_args] + for actual_arg, actual_type, expected_type in zip(actual_args, actual_types, expected_types): + if isinstance(actual_type, ErrorTypeSymbol): + code, message = Messages.get_type_could_not_be_derived(actual_arg) + Logger.log_message(code=code, message=message, log_level=LoggingLevel.ERROR, + error_position=actual_arg.get_source_position()) + return + + if isinstance(expected_type, VariadicTypeSymbol): + # variadic type symbol accepts anything + return + + if not actual_type.equals(expected_type) and not isinstance(expected_type, TemplateTypeSymbol): + TypeCaster.try_to_recover_or_error(expected_type, actual_type, actual_arg) diff --git a/pynestml/transformers/convolutions_transformer.py b/pynestml/transformers/convolutions_transformer.py new file mode 100644 index 000000000..2146dae10 --- /dev/null +++ b/pynestml/transformers/convolutions_transformer.py @@ -0,0 +1,876 @@ +# -*- coding: utf-8 -*- +# +# convolutions_transformer.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from __future__ import annotations + +from typing import Any, Dict, List, Sequence, Mapping, Optional, Tuple, Union + +import re + +import odetoolbox +import sympy + +from pynestml.codegeneration.printers.ast_printer import ASTPrinter +from pynestml.codegeneration.printers.constant_printer import ConstantPrinter +from pynestml.codegeneration.printers.nestml_expression_printer import NESTMLExpressionPrinter +from pynestml.codegeneration.printers.nestml_function_call_printer import NESTMLFunctionCallPrinter +from pynestml.codegeneration.printers.nestml_printer import NESTMLPrinter +from pynestml.codegeneration.printers.nestml_simple_expression_printer import NESTMLSimpleExpressionPrinter +from pynestml.codegeneration.printers.nestml_simple_expression_printer_units_as_factors import NESTMLSimpleExpressionPrinterUnitsAsFactors +from pynestml.codegeneration.printers.ode_toolbox_expression_printer import ODEToolboxExpressionPrinter +from pynestml.codegeneration.printers.ode_toolbox_function_call_printer import ODEToolboxFunctionCallPrinter +from pynestml.codegeneration.printers.ode_toolbox_variable_printer import ODEToolboxVariablePrinter +from pynestml.codegeneration.printers.unitless_sympy_simple_expression_printer import UnitlessSympySimpleExpressionPrinter +from pynestml.frontend.frontend_configuration import FrontendConfiguration +from pynestml.meta_model.ast_assignment import ASTAssignment +from pynestml.meta_model.ast_block import ASTBlock +from pynestml.meta_model.ast_data_type import ASTDataType +from pynestml.meta_model.ast_declaration import ASTDeclaration +from pynestml.meta_model.ast_equations_block import ASTEquationsBlock +from pynestml.meta_model.ast_expression import ASTExpression +from pynestml.meta_model.ast_inline_expression import ASTInlineExpression +from pynestml.meta_model.ast_input_port import ASTInputPort +from pynestml.meta_model.ast_kernel import ASTKernel +from pynestml.meta_model.ast_model import ASTModel +from pynestml.meta_model.ast_node import ASTNode +from pynestml.meta_model.ast_node_factory import ASTNodeFactory +from pynestml.meta_model.ast_simple_expression import ASTSimpleExpression +from pynestml.meta_model.ast_small_stmt import ASTSmallStmt +from pynestml.meta_model.ast_variable import ASTVariable +from pynestml.symbols.predefined_functions import PredefinedFunctions +from pynestml.symbols.real_type_symbol import RealTypeSymbol +from pynestml.symbols.symbol import SymbolKind +from pynestml.symbols.variable_symbol import BlockType +from pynestml.transformers.transformer import Transformer +from pynestml.utils.ast_source_location import ASTSourceLocation +from pynestml.utils.ast_utils import ASTUtils +from pynestml.utils.logger import Logger +from pynestml.utils.logger import LoggingLevel +from pynestml.utils.model_parser import ModelParser +from pynestml.utils.string_utils import removesuffix +from pynestml.visitors.ast_parent_visitor import ASTParentVisitor +from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor +from pynestml.visitors.ast_higher_order_visitor import ASTHigherOrderVisitor +from pynestml.visitors.ast_visitor import ASTVisitor + + +class ConvolutionsTransformer(Transformer): + r"""For each convolution that occurs in the model, allocate one or more needed state variables and replace the convolution() calls by these variable names. + + A new event handler (onReceive block) will be generated that increments the new state variables when a spike arrives. The priority of the handler will be clearly defined to be the highest of all (or the lowest of all) event handlers, so that the rest of the code (update block and other event handlers) get a consistent "just before" or "just after" value. + + For instance, + + .. code-block:: nestml + + state: + V_m mV = 0 mV + + equations: + kernel K = exp(-t / tau_syn) + V_m' = -V_m/tau_m + convolve(K, spikes) / C_m + + update: + ... + integrate_odes(V_m) + ... + + would be transformed into + + .. code-block:: nestml + + state: + V_m mV = 0 mV + K__conv__spikes real = 0 + + equations: + V_m' = -V_m/tau_m + K__conv__spikes / C_m + K__conv__spikes' = -K__conv__spikes / tau_syn + + onReceive(spikes, priority=?????????/): + K__conv__spikes += spikes # bump by spike weight + + update: + K__conv__spikes__at_start_of_timestep real = K__conv__spikes # backup old value of the convolution (add as first statement of update block) + + ... + integrate_odes(V_m, K__conv__spikes) # add integrating the convolutions to each integrate_odes() where, for any argument, there is reference to K__conv__spikes in an expression (checked recursively, and including inline expressions). XXX: in practice, it is added to every call that contains keyword arguments, such that it is always integrated. + K__conv__spikes = K__conv__spikes__at_start_of_timestep # restore old value of the convolution + ... + + # add as a last statement: + integrate_odes(K__conv__spikes) + + + If there is a delta kernel, do not increment the dummy variable, but increment the variable on the lhs of the expression. The dummy variable always stays at zero. + + .. code-block:: nestml + + onReceive(spikes, priority=?????????/): + V_m += spikes / C_m # bump by spike weight + + """ + + _default_options = { + "convolution_separator": "__conv__", + "diff_order_symbol": "__d", + "simplify_expression": "sympy.logcombine(sympy.powsimp(sympy.expand(expr)))" + } + + def __init__(self, options: Optional[Mapping[str, Any]] = None): + super(Transformer, self).__init__(options) + + # ODE-toolbox printers + self._constant_printer = ConstantPrinter() + self._ode_toolbox_variable_printer = ODEToolboxVariablePrinter(None) + self._ode_toolbox_function_call_printer = ODEToolboxFunctionCallPrinter(None) + self._ode_toolbox_printer = ODEToolboxExpressionPrinter(simple_expression_printer=UnitlessSympySimpleExpressionPrinter(variable_printer=self._ode_toolbox_variable_printer, + constant_printer=self._constant_printer, + function_call_printer=self._ode_toolbox_function_call_printer)) + self._ode_toolbox_variable_printer._expression_printer = self._ode_toolbox_printer + self._ode_toolbox_function_call_printer._expression_printer = self._ode_toolbox_printer + + def transform(self, models: Union[ASTNode, Sequence[ASTNode]]) -> Union[ASTNode, Sequence[ASTNode]]: + r"""Transform a model or a list of models. Return an updated model or list of models.""" + for model in models: + print("-------- MODEL BEFORE TRANSFORM ------------") + print(model) + kernel_buffers = self.generate_kernel_buffers(model) + delta_factors = self.get_delta_factors_from_convolutions(model) + + print("Delta factors: ") + for k, v in delta_factors.items(): + print(" -> var = " + str(k[0]) + ", inport = " + str(k[1]) + ", expr = " + str(v)) + + odetoolbox_indict = self.transform_kernels_to_json(model, kernel_buffers) + print("odetoolbox indict: " + str(odetoolbox_indict)) + odetoolbox.Config.config["differential_order_symbol"] = "___D" + solvers_json, shape_sys, shapes = odetoolbox._analysis(odetoolbox_indict, + disable_stiffness_check=True, + disable_analytic_solver=True, + preserve_expressions=True, + simplify_expression=self.get_option("simplify_expression"), + log_level=FrontendConfiguration.logging_level) + odetoolbox.Config.config["differential_order_symbol"] = "__d" + print("odetoolbox outdict: " + str(solvers_json)) + + self.replace_convolve_calls_with_buffers_(model) + delta_factors.update(self.get_delta_factors_from_input_port_references(model)) + self.remove_initial_values_for_kernels(model) + self.create_initial_values_for_kernels(model, solvers_json, kernel_buffers) + self.create_spike_update_event_handlers(model, solvers_json, delta_factors) + self.remove_kernel_definitions_from_equations_blocks(model) + self.add_kernel_variables_to_integrate_odes_calls(model, solvers_json) + self.add_restore_kernel_variables_to_start_of_timestep(model, solvers_json) + self.add_temporary_kernel_variables_copy(model, solvers_json) + self.add_integrate_odes_call_for_kernel_variables(model, solvers_json) + self.add_convolution_equations(model, solvers_json) + self.replace_port_variable_names_with_buffers_(model) + + print("-------- MODEL AFTER TRANSFORM ------------") + print(model) + print("-------------------------------------------") + + return models + + def add_restore_kernel_variables_to_start_of_timestep(self, model, solvers_json): + r"""For each integrate_odes() call in the model, append statements restoring the kernel variables to the values at the start of the timestep""" + + var_names = [] + for solver_dict in solvers_json: + if solver_dict is None: + continue + + for var_name, expr in solver_dict["initial_values"].items(): + var_names.append(var_name) + + class IntegrateODEsFunctionCallVisitor(ASTVisitor): + all_args = None + + def __init__(self): + super().__init__() + + def visit_small_stmt(self, node: ASTSmallStmt): + self._visit(node) + + def visit_simple_expression(self, node: ASTSimpleExpression): + self._visit(node) + + def _visit(self, node): + if node.is_function_call() and node.get_function_call().get_name() == PredefinedFunctions.INTEGRATE_ODES: + parent_stmt = node.get_parent() + parent_block = parent_stmt.get_parent() + assert isinstance(parent_block, ASTBlock) + idx = parent_block.stmts.index(parent_stmt) + + for i, var_name in enumerate(var_names): + var = ASTNodeFactory.create_ast_variable(var_name + "__at_start_of_timestep") + var.update_scope(parent_block.get_scope()) + expr = ASTNodeFactory.create_ast_simple_expression(variable=var) + ast_assignment = ASTNodeFactory.create_ast_assignment(lhs=ASTUtils.get_variable_by_name(model, var_name), + is_direct_assignment=True, + expression=expr, source_position=ASTSourceLocation.get_added_source_position()) + ast_assignment.update_scope(parent_block.get_scope()) + ast_small_stmt = ASTNodeFactory.create_ast_small_stmt(assignment=ast_assignment) + ast_small_stmt.update_scope(parent_block.get_scope()) + ast_stmt = ASTNodeFactory.create_ast_stmt(small_stmt=ast_small_stmt) + ast_stmt.update_scope(parent_block.get_scope()) + + parent_block.stmts.insert(idx + i + 1, ast_stmt) + + model.accept(IntegrateODEsFunctionCallVisitor()) + + def add_kernel_variables_to_integrate_odes_calls(self, model, solvers_json): + for solver_dict in solvers_json: + if solver_dict is None: + continue + + for var_name, expr in solver_dict["initial_values"].items(): + var = ASTUtils.get_variable_by_name(model, var_name) + ASTUtils.add_state_var_to_integrate_odes_calls(model, var) + + model.accept(ASTParentVisitor()) + + def add_integrate_odes_call_for_kernel_variables(self, model, solvers_json): + var_names = [] + for solver_dict in solvers_json: + if solver_dict is None: + continue + + for var_name, expr in solver_dict["initial_values"].items(): + var_names.append(var_name) + + if var_names: + args = ASTUtils.resolve_variables_to_simple_expressions(model, var_names) + ast_function_call = ASTNodeFactory.create_ast_function_call("integrate_odes", args) + ASTUtils.add_function_call_to_update_block(ast_function_call, model) + model.accept(ASTParentVisitor()) + + def add_temporary_kernel_variables_copy(self, model, solvers_json): + var_names = [] + for solver_dict in solvers_json: + if solver_dict is None: + continue + + for var_name, expr in solver_dict["initial_values"].items(): + var_names.append(var_name) + + if not model.get_update_blocks(): + model.create_empty_update_block() + + scope = model.get_update_blocks()[0].scope + + for var_name in var_names: + var = ASTNodeFactory.create_ast_variable(var_name + "__at_start_of_timestep") + var.scope = scope + expr = ASTNodeFactory.create_ast_simple_expression(variable=ASTUtils.get_variable_by_name(model, var_name)) + ast_declaration = ASTNodeFactory.create_ast_declaration(variables=[var], + data_type=ASTDataType(is_real=True), + expression=expr, source_position=ASTSourceLocation.get_added_source_position()) + ast_declaration.update_scope(scope) + ast_small_stmt = ASTNodeFactory.create_ast_small_stmt(declaration=ast_declaration) + ast_small_stmt.update_scope(scope) + ast_stmt = ASTNodeFactory.create_ast_stmt(small_stmt=ast_small_stmt) + ast_stmt.update_scope(scope) + + model.get_update_blocks()[0].get_block().stmts.insert(0, ast_stmt) + + model.accept(ASTParentVisitor()) + model.accept(ASTSymbolTableVisitor()) + + def construct_kernel_spike_buf_name(self, kernel_var_name: str, spike_input_port: ASTInputPort, order: int, diff_order_symbol: Optional[str] = None): + """ + Construct a kernel-buffer name as ``KERNEL_NAME__conv__INPUT_PORT_NAME`` + + For example, if the kernel is + .. code-block:: + kernel I_kernel = exp(-t / tau_x) + + and the input port is + .. code-block:: + pre_spikes nS <- spike + + then the constructed variable will be ``I_kernel__conv__pre_pikes`` + """ + assert type(kernel_var_name) is str + assert type(order) is int + + if isinstance(spike_input_port, ASTSimpleExpression): + spike_input_port = spike_input_port.get_variable() + + if not isinstance(spike_input_port, str): + spike_input_port_name = spike_input_port.get_name() + else: + spike_input_port_name = spike_input_port + + if isinstance(spike_input_port, ASTVariable): + if spike_input_port.has_vector_parameter(): + spike_input_port_name += "_VEC_" + str(ASTUtils.get_numeric_vector_size(spike_input_port)) + + if not diff_order_symbol: + diff_order_symbol = self.get_option("diff_order_symbol") + + return kernel_var_name.replace("$", "__DOLLAR") + self.get_option("convolution_separator") + spike_input_port_name + diff_order_symbol * order + + def replace_rhs_variable(self, expr: ASTExpression, variable_name_to_replace: str, kernel_var: ASTVariable, + spike_buf: ASTInputPort): + """ + Replace variable names in definitions of kernel dynamics + :param expr: expression in which to replace the variables + :param variable_name_to_replace: variable name to replace in the expression + :param kernel_var: kernel variable instance + :param spike_buf: input port instance + :return: + """ + def replace_kernel_var(node): + if type(node) is ASTSimpleExpression \ + and node.is_variable() \ + and node.get_variable().get_name() == variable_name_to_replace: + var_order = node.get_variable().get_differential_order() + new_variable_name = self.construct_kernel_spike_buf_name( + kernel_var.get_name(), spike_buf, var_order - 1, diff_order_symbol="'") + new_variable = ASTVariable(new_variable_name, var_order) + new_variable.set_source_position(node.get_variable().get_source_position()) + node.set_variable(new_variable) + + expr.accept(ASTHigherOrderVisitor(visit_funcs=replace_kernel_var)) + + def replace_rhs_variables(self, expr: ASTExpression, kernel_buffers: Mapping[ASTKernel, ASTInputPort]): + """ + Replace variable names in definitions of kernel dynamics. + + Say that the kernel is + + .. code-block:: + + G = -G / tau + + Its variable symbol might be replaced by "G__conv__spikesEx": + + .. code-block:: + + G__conv__spikesEx = -G / tau + + This function updates the right-hand side of `expr` so that it would also read (in this example): + + .. code-block:: + + G__conv__spikesEx = -G__conv__spikesEx / tau + + These equations will later on be fed to ode-toolbox, so we use the symbol "'" to indicate differential order. + + Note that for kernels/systems of ODE of dimension > 1, all variable orders and all variables for this kernel will already be present in `kernel_buffers`. + """ + for kernel, spike_buf in kernel_buffers: + for kernel_var in kernel.get_variables(): + variable_name_to_replace = kernel_var.get_name() + self.replace_rhs_variable(expr, variable_name_to_replace=variable_name_to_replace, + kernel_var=kernel_var, spike_buf=spike_buf) + + @classmethod + def remove_initial_values_for_kernels(cls, model: ASTModel) -> None: + r""" + Remove initial values for original declarations (e.g. g_in, g_in', V_m); these will be replaced with the initial value expressions returned from ODE-toolbox. + """ + symbols_to_remove = set() + for equations_block in model.get_equations_blocks(): + for kernel in equations_block.get_kernels(): + for kernel_var in kernel.get_variables(): + kernel_var_order = kernel_var.get_differential_order() + for order in range(kernel_var_order): + symbol_name = kernel_var.get_name() + "'" * order + symbols_to_remove.add(symbol_name) + + decl_to_remove = set() + for symbol_name in symbols_to_remove: + for state_block in model.get_state_blocks(): + for decl in state_block.get_declarations(): + if len(decl.get_variables()) == 1: + if decl.get_variables()[0].get_name() == symbol_name: + decl_to_remove.add(decl) + else: + for var in decl.get_variables(): + if var.get_name() == symbol_name: + decl.variables.remove(var) + + for decl in decl_to_remove: + for state_block in model.get_state_blocks(): + if decl in state_block.get_declarations(): + state_block.get_declarations().remove(decl) + + def create_initial_values_for_kernels(self, model: ASTModel, solver_dicts: List[Dict], kernels: List[ASTKernel]) -> None: + r""" + Add the variables used in kernels from the ode-toolbox result dictionary as ODEs in NESTML AST + """ + for solver_dict in solver_dicts: + if solver_dict is None: + continue + + for var_name, expr in solver_dict["initial_values"].items(): + spike_in_port_name = var_name.split(self.get_option("convolution_separator"))[1] + spike_in_port_name = spike_in_port_name.split("__d")[0] + spike_in_port_name = spike_in_port_name.split("___D")[0] + + if "_VEC_" in spike_in_port_name: + spike_in_port_name, _ = spike_in_port_name.split("_VEC_") + + spike_in_port = ASTUtils.get_input_port_by_name(model.get_input_blocks(), spike_in_port_name) + type_str = "real" + if spike_in_port: + differential_order: int = len(re.findall("__d", var_name)) + if differential_order: + type_str = "(s**-" + str(differential_order) + ")" + + expr = "0 " + type_str # for kernels, "initial value" returned by ode-toolbox is actually the increment value; the actual initial value is 0 (property of the convolution) + if not ASTUtils.declaration_in_state_block(model, var_name): + ASTUtils.add_declaration_to_state_block(model, var_name, expr, type_str) + + def is_delta_kernel(self, kernel: ASTKernel) -> bool: + """ + Catches definition of kernel, or reference (function call or variable name) of a delta kernel function. + """ + if not isinstance(kernel, ASTKernel): + return False + + if len(kernel.get_variables()) != 1: + # delta kernel not allowed if more than one variable is defined in this kernel + return False + + expr = kernel.get_expressions()[0] + + rhs_is_delta_kernel = type(expr) is ASTSimpleExpression \ + and expr.is_function_call() \ + and expr.get_function_call().get_scope().resolve_to_symbol(expr.get_function_call().get_name(), SymbolKind.FUNCTION).equals(PredefinedFunctions.name2function["delta"]) + + rhs_is_multiplied_delta_kernel = type(expr) is ASTExpression \ + and type(expr.get_rhs()) is ASTSimpleExpression \ + and expr.get_rhs().is_function_call() \ + and expr.get_rhs().get_function_call().get_scope().resolve_to_symbol(expr.get_rhs().get_function_call().get_name(), SymbolKind.FUNCTION).equals(PredefinedFunctions.name2function["delta"]) + + return rhs_is_delta_kernel or rhs_is_multiplied_delta_kernel + + def replace_convolve_calls_with_buffers_(self, model: ASTModel) -> None: + r""" + Replace all occurrences of `convolve(kernel[']^n, spike_input_port)` with the corresponding buffer variable, e.g. `g_E__conv__spikes_exc[__d]^n` for a kernel named `g_E` and a spike input port named `spikes_exc`. + """ + + def replace_function_call_through_var(_expr=None): + if _expr.is_function_call() and _expr.get_function_call().get_name() == "convolve": + convolve = _expr.get_function_call() + el = (convolve.get_args()[0], convolve.get_args()[1]) + sym = convolve.get_args()[0].get_scope().resolve_to_symbol( + convolve.get_args()[0].get_variable().name, SymbolKind.VARIABLE) + if sym.block_type == BlockType.INPUT: + # swap elements + el = (el[1], el[0]) + var = el[0].get_variable() + spike_input_port = el[1].get_variable() + kernel = model.get_kernel_by_name(var.get_name()) + + _expr.set_function_call(None) + if self.is_delta_kernel(kernel): + # special case for delta kernels: they will be incremented elsewhere + _expr.numeric_literal = 0. + else: + buffer_var = self.construct_kernel_spike_buf_name(var.get_name(), spike_input_port, var.get_differential_order() - 1) + ast_variable = ASTVariable(buffer_var) + ast_variable.set_source_position(_expr.get_source_position()) + _expr.set_variable(ast_variable) + + def func(x): + return replace_function_call_through_var(x) if isinstance(x, ASTSimpleExpression) else True + + for equations_block in model.get_equations_blocks(): + equations_block.accept(ASTHigherOrderVisitor(func)) + + def replace_port_variable_names_with_buffers_(self, model: ASTModel) -> None: + r""" + Replace all occurrences of ``spike_input_port`` with the numeric literal zero. + """ + + spike_inports = model.get_spike_input_ports() + + def replace_inport_through_numeric_literal(_expr=None): + if _expr.variable: + var = _expr.variable + for inport in spike_inports: + if var.get_name() == inport.name: + _expr.variable = None + _expr.numeric_literal = 0. + + def func(x): + return replace_inport_through_numeric_literal(x) if isinstance(x, ASTSimpleExpression) else True + + for equations_block in model.get_equations_blocks(): + equations_block.accept(ASTHigherOrderVisitor(func)) + + def generate_kernel_buffers(self, model: ASTModel) -> Mapping[ASTKernel, ASTInputPort]: + r""" + For every occurrence of a convolution of the form `convolve(var, spike_buf)`: add the element `(kernel, spike_buf)` to the set, with `kernel` being the kernel that contains variable `var`. + """ + kernel_buffers = set() + for equations_block in model.get_equations_blocks(): + convolve_calls = ASTUtils.get_convolve_function_calls(equations_block) + print("convolve_calls = " + " ".join([str(s) for s in convolve_calls])) + for convolve in convolve_calls: + el = (convolve.get_args()[0], convolve.get_args()[1]) + sym = convolve.get_args()[0].get_scope().resolve_to_symbol(convolve.get_args()[0].get_variable().name, SymbolKind.VARIABLE) + if sym is None: + raise Exception("No initial value(s) defined for kernel with variable \"" + + convolve.get_args()[0].get_variable().get_complete_name() + "\"") + if sym.block_type == BlockType.INPUT: + # swap the order + el = (el[1], el[0]) + + # find the corresponding kernel object + var = el[0].get_variable() + assert var is not None + kernel = model.get_kernel_by_name(var.get_name()) + assert kernel is not None, "In convolution \"convolve(" + str(var.name) + ", " + str( + el[1]) + ")\": no kernel by name \"" + var.get_name() + "\" found in model." + + el = (kernel, el[1]) + if not (str(el[0]), str(el[1])) in [(str(el[0]), str(el[1])) for el in kernel_buffers]: + kernel_buffers.add(el) + + return kernel_buffers + + def add_convolution_equations(self, model, solver_dicts): + if not model.get_equations_blocks(): + ASTUtils.create_equations_block(model) + + assert len(model.get_equations_blocks()) == 1, "More than one equations block not yet supported!" + + equations_block = model.get_equations_blocks()[0] + + for solver_dict in solver_dicts: + if solver_dict is None: + continue + + for var_name, expr_str in solver_dict["update_expressions"].items(): + expr = ModelParser.parse_expression(expr_str) + expr.update_scope(model.get_scope()) + expr.accept(ASTSymbolTableVisitor()) + + if isinstance(expr.type, RealTypeSymbol): + expr = ModelParser.parse_expression("(" + expr_str + ") / ms") # change per second XXX: why ms? this should be simulation platform target-dependent. See + expr.update_scope(model.get_scope()) + expr.accept(ASTSymbolTableVisitor()) + + var = ASTNodeFactory.create_ast_variable(var_name, differential_order=1, source_position=ASTSourceLocation.get_added_source_position()) + var.update_scope(equations_block.get_scope()) + ast_ode_equation = ASTNodeFactory.create_ast_ode_equation(lhs=var, rhs=expr, source_position=ASTSourceLocation.get_added_source_position()) + ast_ode_equation.update_scope(equations_block.get_scope()) + equations_block.declarations.append(ast_ode_equation) + + model.accept(ASTParentVisitor()) + model.accept(ASTSymbolTableVisitor()) + + def remove_kernel_definitions_from_equations_blocks(self, model: ASTModel) -> ASTDeclaration: + r""" + Removes all kernels in equations blocks. + """ + for equations_block in model.get_equations_blocks(): + decl_to_remove = set() + for decl in equations_block.get_declarations(): + if type(decl) is ASTKernel: + decl_to_remove.add(decl) + + for decl in decl_to_remove: + equations_block.get_declarations().remove(decl) + + def transform_kernels_to_json(self, model: ASTModel, kernel_buffers: List[Tuple[ASTKernel, ASTInputPort]]) -> Dict: + """ + Converts AST node to a JSON representation suitable for passing to ode-toolbox. + + Each kernel has to be generated for each spike buffer convolve in which it occurs, e.g. if the NESTML model code contains the statements + + .. code-block:: + + convolve(G, exc_spikes) + convolve(G, inh_spikes) + + then `kernel_buffers` will contain the pairs `(G, exc_spikes)` and `(G, inh_spikes)`, from which two ODEs will be generated, with dynamical state (variable) names `G__conv__exc_spikes` and `G__conv__inh_spikes`. + """ + odetoolbox_indict = {} + odetoolbox_indict["dynamics"] = [] + + for kernel, spike_input_port in kernel_buffers: + for kernel_var in kernel.get_variables(): + expr = ASTUtils.get_expr_from_kernel_var(kernel, kernel_var.get_complete_name()) + kernel_order = kernel_var.get_differential_order() + kernel_X_spike_buf_name_ticks = self.construct_kernel_spike_buf_name(kernel_var.get_name(), spike_input_port, kernel_order, diff_order_symbol="'") + + self.replace_rhs_variables(expr, kernel_buffers) + + if self.is_delta_kernel(kernel): + # ODE-toolbox does not know how to handle "delta(t)", so replace this by a constant rhs so it generates a dummy (do-nothing) update expression + expr = "1" + + entry = {"expression": kernel_X_spike_buf_name_ticks + " = " + str(expr), "initial_values": {}} + + # initial values need to be declared for order 1 up to kernel order (e.g. none for kernel function + # f(t) = ...; 1 for kernel ODE f'(t) = ...; 2 for f''(t) = ... and so on) + for order in range(kernel_order): + iv_sym_name_ode_toolbox = self.construct_kernel_spike_buf_name(kernel_var.get_name(), spike_input_port, order, diff_order_symbol="'") + symbol_name_ = kernel_var.get_name() + "'" * order + symbol = model.get_scope().resolve_to_symbol(symbol_name_, SymbolKind.VARIABLE) + assert symbol is not None, "Could not find initial value for variable " + symbol_name_ + initial_value_expr = symbol.get_declaring_expression() + assert initial_value_expr is not None, "No initial value found for variable name " + symbol_name_ + entry["initial_values"][iv_sym_name_ode_toolbox] = self._ode_toolbox_printer.print(initial_value_expr) + + odetoolbox_indict["dynamics"].append(entry) + + odetoolbox_indict["parameters"] = {} + for parameters_block in model.get_parameters_blocks(): + for decl in parameters_block.get_declarations(): + for var in decl.variables: + odetoolbox_indict["parameters"][var.get_complete_name()] = self._ode_toolbox_printer.print(decl.get_expression()) + + return odetoolbox_indict + + def get_delta_factors_from_convolutions(self, model: ASTModel) -> dict: + r""" + For every occurrence of a convolution of the form `x^(n) = a * convolve(kernel, inport) + ...` where `kernel` is a delta function, add the element `(x^(n), inport) --> a` to the set. + """ + delta_factors = {} + + for equations_block in model.get_equations_blocks(): + for ode_eq in equations_block.get_ode_equations(): + var = ode_eq.get_lhs() + expr = ode_eq.get_rhs() + conv_calls = ASTUtils.get_convolve_function_calls(expr) + for conv_call in conv_calls: + assert len(conv_call.args) == 2, "convolve() function call should have precisely two arguments: kernel and spike input port" + kernel = conv_call.args[0] + if self.is_delta_kernel(model.get_kernel_by_name(kernel.get_variable().get_name())): + inport = conv_call.args[1].get_variable() + factor_str = self.get_factor_str_from_expr_and_inport(expr, str(conv_call)) + assert factor_str + delta_factors[(var, inport)] = factor_str + + return delta_factors + + def get_factor_str_from_expr_and_inport(self, expr, sub_expr): + from sympy.physics.units import Quantity, Unit, siemens, milli, micro, nano, pico, femto, kilo, mega, volt, ampere, ohm, farad, second, meter, hertz + from sympy import sympify + + units = { + 'V': volt, # Volt + 'mV': milli * volt, # Millivolt (10^-3 V) + 'uV': micro * volt, # Microvolt (10^-6 V) + 'nV': nano * volt, # Nanovolt (10^-9 V) + + 'S': siemens, # Ampere + 'nS': nano * siemens, # Ampere + + 'A': ampere, # Ampere + 'mA': milli * ampere, # Milliampere (10^-3 A) + 'uA': micro * ampere, # Microampere (10^-6 A) + 'nA': nano * ampere, # Nanoampere (10^-9 A) + + 'Ohm': ohm, # Ohm + 'kOhm': kilo * ohm, # Kiloohm (10^3 Ohm) + 'MOhm': mega * ohm, # Megaohm (10^6 Ohm) + + 'F': farad, # Farad + 'uF': micro * farad, # Microfarad (10^-6 F) + 'nF': nano * farad, # Nanofarad (10^-9 F) + 'pF': pico * farad, # Picofarad (10^-12 F) + 'fF': femto * farad, # Femtofarad (10^-15 F) + + 's': second, # Second + 'ms': milli * second, # Millisecond (10^-3 s) + 'us': micro * second, # Microsecond (10^-6 s) + 'ns': nano * second, # Nanosecond (10^-9 s) + + 'Hz': hertz, # Hertz (1/s) + 'kHz': kilo * hertz, # Kilohertz (10^3 Hz) + 'MHz': mega * hertz, # Megahertz (10^6 Hz) + + 'm': meter, # Meter + 'mm': milli * meter, # Millimeter (10^-3 m) + 'um': micro * meter, # Micrometer (10^-6 m) + 'nm': nano * meter, # Nanometer (10^-9 m) + } + + printer = NESTMLPrinter() + printer._expression_printer = ODEToolboxExpressionPrinter(simple_expression_printer=None) + printer._constant_printer = ConstantPrinter() + printer._function_call_printer = NESTMLFunctionCallPrinter(expression_printer=printer._expression_printer) + printer._variable_printer = ODEToolboxVariablePrinter(expression_printer=printer._expression_printer) + printer._simple_expression_printer = NESTMLSimpleExpressionPrinterUnitsAsFactors(variable_printer=printer._variable_printer, function_call_printer=printer._function_call_printer, constant_printer=printer._constant_printer) + printer._expression_printer._simple_expression_printer = printer._simple_expression_printer + + expr_str = printer.print(expr) + + print("In get_delta_factors_from_input_port_references(): parsing " + expr_str) + sympy_expr = sympify(expr_str, locals=units) + sympy_expr = sympy.expand(sympy_expr) + sympy_conv_expr = sympy.parsing.sympy_parser.parse_expr(sub_expr) + factor_str = [] + for term in sympy.Add.make_args(sympy_expr): + if term.find(sympy_conv_expr): + _expr = str(term.replace(sympy_conv_expr, 1)) + factor_str.append(_expr) + + factor_str = " + ".join(factor_str) + + return factor_str + + def get_delta_factors_from_input_port_references(self, model: ASTModel) -> dict: + r""" + For every occurrence of a convolution of the form ``x^(n) = a * inport + ...``, add the element `(x^(n), inport) --> a` to the set. + """ + delta_factors = {} + print("-----") + print("get_delta_factors_from_input_port_references") + + spike_inports = model.get_spike_input_ports() + for equations_block in model.get_equations_blocks(): + for ode_eq in equations_block.get_ode_equations(): + var = ode_eq.get_lhs() + expr = ode_eq.get_rhs() + + for inport in spike_inports: + # inport = ASTUtils.get_input_port_by_name(model.get_input_blocks(), inport.name) + + inport = ASTNodeFactory.create_ast_variable(inport.name) + inport.update_scope(equations_block.get_scope()) + + factor_str = self.get_factor_str_from_expr_and_inport(expr, inport.name) + + if factor_str: + delta_factors[(var, inport)] = factor_str + + for k, v in delta_factors.items(): + print("var = " + str(k[0]) + ", inport = " + str(k[1]) + ", expr = " + str(v)) + print("-----") + + return delta_factors + + def create_spike_update_event_handlers(self, model: ASTModel, solver_dicts, delta_factors) -> Tuple[Dict[str, ASTAssignment], Dict[str, ASTAssignment]]: + r""" + Generate the equations that update the dynamical variables when incoming spikes arrive. To be invoked after + ode-toolbox. + + For example, a resulting `assignment_str` could be "I_kernel_in += (inh_spikes/nS) * 1". The values are taken from the initial values for each corresponding dynamical variable, either from ode-toolbox or directly from user specification in the model. + from the initial values for each corresponding dynamical variable, either from ode-toolbox or directly from + user specification in the model. + + Note that for kernels, `initial_values` actually contains the increment upon spike arrival, rather than the + initial value of the corresponding ODE dimension. + """ + + spike_in_port_to_stmts = {} + for solver_dict in solver_dicts: + for var, expr in solver_dict["initial_values"].items(): + expr = str(expr) + if expr in ["0", "0.", "0.0"]: + continue # skip adding the statement if we are only adding zero + + spike_in_port_name = var.split(self.get_option("convolution_separator"))[1] + spike_in_port_name = spike_in_port_name.split("__d")[0] + spike_in_port_name = spike_in_port_name.split("___D")[0] + + if "_VEC_" in spike_in_port_name: + port_name, port_index = spike_in_port_name.split("_VEC_") + spike_in_port_name = port_name + "[" + port_index + "]" + + spike_in_port = ASTUtils.get_input_port_by_name(model.get_input_blocks(), spike_in_port_name) + type_str = "real" + + assert spike_in_port + differential_order: int = len(re.findall("__d", var)) + if differential_order: + type_str = "(s**-" + str(differential_order) + ")" + + assignment_str = var + " += " + assignment_str += "1000. * (" + str(spike_in_port_name) + ")" # XXX: not clear where the factor 1E3 comes from + if not expr in ["1.", "1.0", "1"]: + assignment_str += " * (" + expr + ")" + + kernel = model.get_kernel_by_name(var.split(self.get_option("convolution_separator"))[0]) + if kernel and self.is_delta_kernel(kernel): + continue + + ast_assignment = ModelParser.parse_assignment(assignment_str) + ast_assignment.update_scope(model.get_scope()) + ast_assignment.accept(ASTSymbolTableVisitor()) + + ast_small_stmt = ASTNodeFactory.create_ast_small_stmt(assignment=ast_assignment) + ast_stmt = ASTNodeFactory.create_ast_stmt(small_stmt=ast_small_stmt) + + if not spike_in_port_name in spike_in_port_to_stmts.keys(): + spike_in_port_to_stmts[spike_in_port_name] = [] + + spike_in_port_to_stmts[spike_in_port_name].append(ast_stmt) + + for k, factor in delta_factors.items(): + var = k[0] + inport = k[1] + assignment_str = var.get_name() + "'" * (var.get_differential_order() - 1) + " += " + if not factor in ["1.", "1.0", "1"]: + factor_expr = ModelParser.parse_expression(factor) + factor_expr.update_scope(model.get_scope()) + factor_expr.accept(ASTSymbolTableVisitor()) + assignment_str += "(" + self._ode_toolbox_printer.print(factor_expr) + ") * " + + if "_is_post_port" in dir(inport) and inport._is_post_port: + orig_port_name = inport[:inport.index("__for_")] + buffer_type = model.paired_synapse.get_scope().resolve_to_symbol(orig_port_name, SymbolKind.VARIABLE).get_type_symbol() + else: + buffer_type = model.get_scope().resolve_to_symbol(inport.get_name(), SymbolKind.VARIABLE).get_type_symbol() + + assignment_str += str(inport) + if not buffer_type.print_nestml_type() in ["1.", "1.0", "1"]: + assignment_str += " / (" + buffer_type.print_nestml_type() + ")" + ast_assignment = ModelParser.parse_assignment(assignment_str) + ast_assignment.update_scope(model.get_scope()) + ast_assignment.accept(ASTSymbolTableVisitor()) + ast_small_stmt = ASTNodeFactory.create_ast_small_stmt(assignment=ast_assignment) + ast_small_stmt.update_scope(model.get_scope()) + ast_small_stmt.accept(ASTSymbolTableVisitor()) + ast_stmt = ASTNodeFactory.create_ast_stmt(small_stmt=ast_small_stmt) + ast_stmt.update_scope(model.get_scope()) + ast_stmt.accept(ASTSymbolTableVisitor()) + + inport_name = inport.get_name() + if inport.has_vector_parameter(): + inport_name += "[" + str(ASTUtils.get_numeric_vector_size(inport)) + "]" + + if not inport_name in spike_in_port_to_stmts.keys(): + spike_in_port_to_stmts[inport_name] = [] + + spike_in_port_to_stmts[inport_name].append(ast_stmt) + + # for every input port, add an onreceive block with its update statements + for in_port, stmts in spike_in_port_to_stmts.items(): + stmts_block = ASTNodeFactory.create_ast_block(stmts, ASTSourceLocation.get_added_source_position()) + on_receive_block = ASTNodeFactory.create_ast_on_receive_block(stmts_block, + in_port, + const_parameters=None, # XXX: TODO: add priority here! + source_position=ASTSourceLocation.get_added_source_position()) + + model.get_body().get_body_elements().append(on_receive_block) + + model.accept(ASTParentVisitor()) diff --git a/pynestml/transformers/synapse_post_neuron_transformer.py b/pynestml/transformers/synapse_post_neuron_transformer.py index 68cc70a62..53f7bddf2 100644 --- a/pynestml/transformers/synapse_post_neuron_transformer.py +++ b/pynestml/transformers/synapse_post_neuron_transformer.py @@ -22,7 +22,9 @@ from __future__ import annotations from typing import Any, Sequence, Mapping, Optional, Union +from pynestml.cocos.co_cos_manager import CoCosManager +from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.frontend.frontend_configuration import FrontendConfiguration from pynestml.meta_model.ast_assignment import ASTAssignment from pynestml.meta_model.ast_equations_block import ASTEquationsBlock @@ -165,51 +167,6 @@ def get_neuron_var_name_from_syn_port_name(self, port_name: str, neuron_name: st return None - def get_convolve_with_not_post_vars(self, nodes: Union[ASTEquationsBlock, Sequence[ASTEquationsBlock]], neuron_name: str, synapse_name: str, parent_node: ASTNode): - class ASTVariablesUsedInConvolutionVisitor(ASTVisitor): - _variables = [] - - def __init__(self, node: ASTNode, parent_node: ASTNode, codegen_class): - super(ASTVariablesUsedInConvolutionVisitor, self).__init__() - self.node = node - self.parent_node = parent_node - self.codegen_class = codegen_class - - def visit_function_call(self, node): - func_name = node.get_name() - if func_name == "convolve": - symbol_buffer = node.get_scope().resolve_to_symbol(str(node.get_args()[1]), - SymbolKind.VARIABLE) - input_port = ASTUtils.get_input_port_by_name( - self.parent_node.get_input_blocks(), symbol_buffer.name) - if input_port and not self.codegen_class.is_post_port(input_port.name, neuron_name, synapse_name): - kernel_name = node.get_args()[0].get_variable().name - self._variables.append(kernel_name) - - found_parent_assignment = False - node_ = node - while not found_parent_assignment: - node_ = node_.get_parent() - # XXX TODO also needs to accept normal ASTExpression, ASTAssignment? - if isinstance(node_, ASTInlineExpression): - found_parent_assignment = True - var_name = node_.get_variable_name() - self._variables.append(var_name) - - if not nodes: - return [] - - if isinstance(nodes, ASTNode): - nodes = [nodes] - - variables = [] - for node in nodes: - visitor = ASTVariablesUsedInConvolutionVisitor(node, parent_node, self) - node.accept(visitor) - variables.extend(visitor._variables) - - return variables - def get_all_variables_assigned_to(self, node): r"""Return a list of all variables that are assigned to in ``node``.""" class ASTAssignedToVariablesFinderVisitor(ASTVisitor): @@ -272,13 +229,6 @@ def transform_neuron_synapse_pair_(self, neuron: ASTModel, synapse: ASTModel): all_state_vars = [var.get_complete_name() for var in all_state_vars] - # add names of convolutions - all_state_vars += ASTUtils.get_all_variables_used_in_convolutions(synapse.get_equations_blocks(), synapse) - - # add names of kernels - kernel_buffers = ASTUtils.generate_kernel_buffers(synapse, synapse.get_equations_blocks()) - all_state_vars += [var.name for k in kernel_buffers for var in k[0].variables] - # exclude certain variables from being moved: # exclude any variable assigned to in any block that is not connected to a postsynaptic port strictly_synaptic_vars = ["t"] # "seed" this with the predefined variable t @@ -297,28 +247,25 @@ def transform_neuron_synapse_pair_(self, neuron: ASTModel, synapse: ASTModel): for update_block in synapse.get_update_blocks(): strictly_synaptic_vars += self.get_all_variables_assigned_to(update_block) - # exclude convolutions if they are not with a postsynaptic variable - convolve_with_not_post_vars = self.get_convolve_with_not_post_vars(synapse.get_equations_blocks(), neuron.name, synapse.name, synapse) - # exclude all variables that depend on the ones that are not to be moved strictly_synaptic_vars_dependent = ASTUtils.recursive_dependent_variables_search(strictly_synaptic_vars, synapse) # do set subtraction - syn_to_neuron_state_vars = list(set(all_state_vars) - (set(strictly_synaptic_vars) | set(convolve_with_not_post_vars) | set(strictly_synaptic_vars_dependent))) + syn_to_neuron_state_vars = list(set(all_state_vars) - (set(strictly_synaptic_vars) | set(strictly_synaptic_vars_dependent))) # - # collect all the variable/parameter/kernel/function/etc. names used in defining expressions of `syn_to_neuron_state_vars` + # collect all the variable/parameter/function/etc. names used in defining expressions of `syn_to_neuron_state_vars` # recursive_vars_used = ASTUtils.recursive_necessary_variables_search(syn_to_neuron_state_vars, synapse) new_neuron.recursive_vars_used = recursive_vars_used new_neuron._transferred_variables = [neuron_state_var + var_name_suffix - for neuron_state_var in syn_to_neuron_state_vars if new_synapse.get_kernel_by_name(neuron_state_var) is None] + for neuron_state_var in syn_to_neuron_state_vars] # all state variables that will be moved from synapse to neuron syn_to_neuron_state_vars = [] for var_name in recursive_vars_used: - if ASTUtils.get_state_variable_by_name(synapse, var_name) or ASTUtils.get_inline_expression_by_name(synapse, var_name) or ASTUtils.get_kernel_by_name(synapse, var_name): + if ASTUtils.get_state_variable_by_name(synapse, var_name) or ASTUtils.get_inline_expression_by_name(synapse, var_name): syn_to_neuron_state_vars.append(var_name) Logger.log_message(None, -1, "State variables that will be moved from synapse to neuron: " + str(syn_to_neuron_state_vars), @@ -444,33 +391,6 @@ def transform_neuron_synapse_pair_(self, neuron: ASTModel, synapse: ASTModel): block_type=BlockType.STATE, mode="move") - # - # mark variables in the neuron pertaining to synapse postsynaptic ports - # - # convolutions with them ultimately yield variable updates when post neuron calls emit_spike() - # - - def mark_post_ports(neuron, synapse, mark_node): - post_ports = [] - - def mark_post_port(_expr=None): - var = None - if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): - var = _expr.get_variable() - elif isinstance(_expr, ASTVariable): - var = _expr - - if var: - var_base_name = var.name[:-len(var_name_suffix)] # prune the suffix - if self.is_post_port(var_base_name, neuron.name, synapse.name): - post_ports.append(var) - var._is_post_port = True - - mark_node.accept(ASTHigherOrderVisitor(lambda x: mark_post_port(x))) - return post_ports - - mark_post_ports(new_neuron, new_synapse, new_neuron) - # # move statements in post receive block from synapse to new_neuron # @@ -563,11 +483,6 @@ def mark_post_port(_expr=None): # replace occurrences of the variables in expressions in the original synapse with calls to the corresponding neuron getters # - # make sure the moved symbols can be resolved in the scope of the neuron (that's where ``ASTExternalVariable._altscope`` will be pointing to) - ast_symbol_table_visitor = ASTSymbolTableVisitor() - ast_symbol_table_visitor.after_ast_rewrite_ = True - new_neuron.accept(ast_symbol_table_visitor) - Logger.log_message( None, -1, "In synapse: replacing variables with suffixed external variable references", None, LoggingLevel.INFO) for state_var in syn_to_neuron_state_vars: @@ -609,9 +524,10 @@ def mark_post_port(_expr=None): new_neuron.accept(ASTParentVisitor()) new_synapse.accept(ASTParentVisitor()) ast_symbol_table_visitor = ASTSymbolTableVisitor() - ast_symbol_table_visitor.after_ast_rewrite_ = True new_neuron.accept(ast_symbol_table_visitor) new_synapse.accept(ast_symbol_table_visitor) + CoCosManager.check_cocos(new_neuron, after_ast_rewrite=True) + CoCosManager.check_cocos(new_synapse, after_ast_rewrite=True) ASTUtils.update_blocktype_for_common_parameters(new_synapse) @@ -621,6 +537,12 @@ def mark_post_port(_expr=None): return new_neuron, new_synapse def transform(self, models: Union[ASTNode, Sequence[ASTNode]]) -> Union[ASTNode, Sequence[ASTNode]]: + # check that there are no convolutions or kernels in the model (these should have been transformed out by the ConvolutionsTransformer) + for model in models: + for equations_block in model.get_equations_blocks(): + assert len(equations_block.get_kernels()) == 0, "Kernels and convolutions should have been removed by ConvolutionsTransformer" + + # transform each (neuron, synapse) pair for neuron_synapse_pair in self.get_option("neuron_synapse_pairs"): neuron_name = neuron_synapse_pair["neuron"] synapse_name = neuron_synapse_pair["synapse"] diff --git a/pynestml/utils/ast_synapse_information_collector.py b/pynestml/utils/ast_synapse_information_collector.py index f5a6763bc..896a2e2a6 100644 --- a/pynestml/utils/ast_synapse_information_collector.py +++ b/pynestml/utils/ast_synapse_information_collector.py @@ -191,10 +191,12 @@ def get_extracted_kernel_args(self, inline_expression: ASTInlineExpression) -> s def get_basic_kernel_variable_names(self, synapse_inline): """ - for every occurence of convolve(port, spikes) generate "port__X__spikes" variable + for every occurence of convolve(port, spikes) generate "port__conv__spikes" variable gather those variables for this synapse inline and return their list - note that those variables will occur as substring in other kernel variables i.e "port__X__spikes__d" or "__P__port__X__spikes__port__X__spikes" + note that those variables will occur as substring in other kernel variables + + i.e "port__conv__spikes__d" or "__P__port__conv__spikes__port__conv__spikes" so we can use the result to identify all the other kernel variables related to the specific synapse inline declaration @@ -207,7 +209,7 @@ def get_basic_kernel_variable_names(self, synapse_inline): kernel_name = kernel_var.get_name() spike_input_port = self.input_port_name_to_input_port[spike_var.get_name( )] - kernel_variable_name = self.construct_kernel_X_spike_buf_name( + kernel_variable_name = self.construct_kernel_spike_buf_name( kernel_name, spike_input_port, order) results.append(kernel_variable_name) @@ -346,4 +348,4 @@ def construct_kernel_X_spike_buf_name(kernel_var_name: str, spike_input_port, or assert type(kernel_var_name) is str assert type(order) is int assert type(diff_order_symbol) is str - return kernel_var_name.replace("$", "__DOLLAR") + "__X__" + str(spike_input_port) + diff_order_symbol * order + return kernel_var_name.replace("$", "__DOLLAR") + "__conv__" + str(spike_input_port) + diff_order_symbol * order diff --git a/pynestml/utils/ast_utils.py b/pynestml/utils/ast_utils.py index d3d6f6ef5..d11e1eec2 100644 --- a/pynestml/utils/ast_utils.py +++ b/pynestml/utils/ast_utils.py @@ -21,14 +21,8 @@ from typing import Dict, Iterable, List, Mapping, Optional, Sequence, Union -import re -import sympy - -import odetoolbox - from pynestml.codegeneration.printers.ast_printer import ASTPrinter from pynestml.codegeneration.printers.cpp_variable_printer import CppVariablePrinter -from pynestml.codegeneration.printers.nestml_printer import NESTMLPrinter from pynestml.frontend.frontend_configuration import FrontendConfiguration from pynestml.generated.PyNestMLLexer import PyNestMLLexer from pynestml.meta_model.ast_assignment import ASTAssignment @@ -66,7 +60,6 @@ from pynestml.utils.messages import Messages from pynestml.utils.string_utils import removesuffix from pynestml.visitors.ast_higher_order_visitor import ASTHigherOrderVisitor -from pynestml.visitors.ast_parent_visitor import ASTParentVisitor from pynestml.visitors.ast_visitor import ASTVisitor @@ -444,6 +437,25 @@ def create_internal_block(cls, model: ASTModel): return model + @classmethod + def create_on_receive_block(cls, model: ASTModel, block: ASTBlock, input_port_name: str) -> ASTModel: + """ + Creates a single onReceive block in the handed over model. + :param model: a single model + :return: the modified model + """ + # local import since otherwise circular dependency + from pynestml.meta_model.ast_node_factory import ASTNodeFactory + block = ASTNodeFactory.create_ast_on_receive_block(block, input_port_name, + ASTSourceLocation.get_added_source_position()) + block.update_scope(model.get_scope()) + model.get_body().get_body_elements().append(block) + + from pynestml.visitors.ast_parent_visitor import ASTParentVisitor + model.accept(ASTParentVisitor()) + + return model + @classmethod def create_state_block(cls, model: ASTModel): r""" @@ -548,18 +560,32 @@ def all_variables_defined_in_block(cls, blocks: Union[ASTBlock, List[ASTBlock]]) return vars @classmethod - def inline_aliases_convolution(cls, inline_expr: ASTInlineExpression) -> bool: - """ - Returns True if and only if the inline expression is of the form ``var type = convolve(...)``. + def add_state_var_to_integrate_odes_calls(cls, model: ASTModel, var: ASTExpression, append_to_no_args_call=False): + r"""Add a state variable to the arguments to each integrate_odes() calls in the model. + + If ``append_to_no_args_call`` is True, the variable is always appended. Otherwise, it is only appended if there are already other arguments in the call to ``integrate_odes()``; the thought behind this is that the variable would be already implicitly included if the call has no arguments at all. """ - expr = inline_expr.get_expression() - if isinstance(expr, ASTExpression): - expr = expr.get_lhs() - if isinstance(expr, ASTSimpleExpression) \ - and expr.is_function_call() \ - and expr.get_function_call().get_name() == PredefinedFunctions.CONVOLVE: - return True - return False + + class AddStateVarToIntegrateODEsCallsVisitor(ASTVisitor): + def visit_function_call(self, node: ASTFunctionCall): + if node.get_name() == PredefinedFunctions.INTEGRATE_ODES: + expr = ASTNodeFactory.create_ast_simple_expression(variable=var.clone()) + if append_to_no_args_call or (not append_to_no_args_call and len(node.args) > 0): + node.args.append(expr) + + model.accept(AddStateVarToIntegrateODEsCallsVisitor()) + + @classmethod + def resolve_variables_to_simple_expressions(cls, model, vars): + """receives a list of variable names (as strings) and returns a list of ASTSimpleExpressions containing each ASTVariable""" + expressions = [] + + for var_name in vars: + node = ASTUtils.get_variable_by_name(model, var_name) + assert node is not None + expressions.append(ASTNodeFactory.create_ast_simple_expression(variable=node)) + + return expressions @classmethod def add_suffix_to_variable_name(cls, var_name: str, astnode: ASTNode, suffix: str, scope=None): @@ -661,20 +687,6 @@ def get_inline_expression_by_name(cls, node, name: str) -> Optional[ASTInlineExp return None - @classmethod - def get_inline_expression_by_constructed_rhs_name(cls, node, name: str) -> Optional[ASTInlineExpression]: - for equations_block in node.get_equations_blocks(): - for inline_expr in equations_block.get_inline_expressions(): - if not ASTUtils.inline_aliases_convolution(inline_expr): - continue - - constructed_name = ASTUtils.construct_kernel_X_spike_buf_name(str(inline_expr.get_expression().get_function_call().get_args()[0]), inline_expr.get_expression().get_function_call().get_args()[1], order=0, suffix="__for_" + node.get_name()) - - if name == constructed_name: - return inline_expr - - return None - @classmethod def get_kernel_by_name(cls, node, name: str) -> Optional[ASTKernel]: for equations_block in node.get_equations_blocks(): @@ -1068,6 +1080,32 @@ def has_equation_with_delay_variable(cls, equations_with_delay_vars: ASTOdeEquat return True return False + @classmethod + def add_function_call_to_update_block(cls, function_call: ASTFunctionCall, model: ASTModel) -> ASTModel: + """ + Adds a single assignment to the end of the update block of the handed over model. + :param function_call: a single function call + :param neuron: a single model instance + :return: the modified model + """ + assert len(model.get_update_blocks()) <= 1, "At most one update block should be present" + + if not model.get_update_blocks(): + model.create_empty_update_block() + + small_stmt = ASTNodeFactory.create_ast_small_stmt(function_call=function_call, + source_position=ASTSourceLocation.get_added_source_position()) + stmt = ASTNodeFactory.create_ast_stmt(small_stmt=small_stmt, + source_position=ASTSourceLocation.get_added_source_position()) + model.get_update_blocks()[0].get_block().get_stmts().append(stmt) + small_stmt.update_scope(model.get_update_blocks()[0].get_block().get_scope()) + stmt.update_scope(model.get_update_blocks()[0].get_block().get_scope()) + + from pynestml.visitors.ast_parent_visitor import ASTParentVisitor + model.accept(ASTParentVisitor()) + + return model + @classmethod def add_declarations_to_internals(cls, neuron: ASTModel, declarations: Mapping[str, str]) -> ASTModel: """ @@ -1251,7 +1289,10 @@ def variable_in_solver(cls, var: str, solver_dicts: List[dict]) -> bool: continue for var_name in solver_dict["state_variables"]: - var_name_base = var_name.split("__X__")[0] + if var_name == var: + return True + + var_name_base = var_name.split("__conv__")[0] if var_name_base == var: return True @@ -1275,7 +1316,7 @@ def variable_in_kernels(cls, var_name: str, kernels: List[ASTKernel]) -> bool: Check if a variable by this name (in ode-toolbox style) is defined in the ode-toolbox solver results """ - var_name_base = var_name.split("__X__")[0] + var_name_base = var_name.split("__conv__")[0] var_name_base = var_name_base.split("__d")[0] var_name_base = var_name_base.replace("__DOLLAR", "$") @@ -1319,7 +1360,7 @@ def get_kernel_var_order_from_ode_toolbox_result(cls, kernel_var: str, solver_di continue for var_name in solver_dict["state_variables"]: - var_name_base = var_name.split("__X__")[0] + var_name_base = var_name.split("__conv__")[0] var_name_base = var_name_base.split("__d")[0] if var_name_base == kernel_var: order = max(order, var_name.count("__d") + 1) @@ -1355,122 +1396,9 @@ def get_expr_from_kernel_var(cls, kernel: ASTKernel, var_name: str) -> Union[AST @classmethod def all_convolution_variable_names(cls, model: ASTModel) -> List[str]: vars = ASTUtils.all_variables_defined_in_block(model.get_state_blocks()) - var_names = [var.get_complete_name() for var in vars if "__X__" in var.get_complete_name()] + var_names = [var.get_complete_name() for var in vars if "__conv__" in var.get_complete_name()] return var_names - @classmethod - def construct_kernel_X_spike_buf_name(cls, kernel_var_name: str, spike_input_port: ASTInputPort, order: int, - diff_order_symbol="__d", suffix=""): - """ - Construct a kernel-buffer name as - - For example, if the kernel is - .. code-block:: - kernel I_kernel = exp(-t / tau_x) - - and the input port is - .. code-block:: - pre_spikes nS <- spike - - then the constructed variable will be 'I_kernel__X__pre_pikes' - """ - assert type(kernel_var_name) is str - assert type(order) is int - assert type(diff_order_symbol) is str - - if isinstance(spike_input_port, ASTSimpleExpression): - spike_input_port = spike_input_port.get_variable() - - if not isinstance(spike_input_port, str): - spike_input_port_name = spike_input_port.get_name() - else: - spike_input_port_name = spike_input_port - - if isinstance(spike_input_port, ASTVariable): - if spike_input_port.has_vector_parameter(): - spike_input_port_name += "_" + str(cls.get_numeric_vector_size(spike_input_port)) - - return kernel_var_name.replace("$", "__DOLLAR") + suffix + "__X__" + spike_input_port_name + diff_order_symbol * order + suffix - - @classmethod - def replace_rhs_variable(cls, expr: ASTExpression, variable_name_to_replace: str, kernel_var: ASTVariable, - spike_buf: ASTInputPort): - """ - Replace variable names in definitions of kernel dynamics - :param expr: expression in which to replace the variables - :param variable_name_to_replace: variable name to replace in the expression - :param kernel_var: kernel variable instance - :param spike_buf: input port instance - :return: - """ - def replace_kernel_var(node): - if type(node) is ASTSimpleExpression \ - and node.is_variable() \ - and node.get_variable().get_name() == variable_name_to_replace: - var_order = node.get_variable().get_differential_order() - new_variable_name = cls.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_buf, var_order - 1, diff_order_symbol="'") - new_variable = ASTVariable(new_variable_name, var_order) - new_variable.set_source_position(node.get_variable().get_source_position()) - node.set_variable(new_variable) - - expr.accept(ASTHigherOrderVisitor(visit_funcs=replace_kernel_var)) - - @classmethod - def replace_rhs_variables(cls, expr: ASTExpression, kernel_buffers: Mapping[ASTKernel, ASTInputPort]): - """ - Replace variable names in definitions of kernel dynamics. - - Say that the kernel is - - .. code-block:: - - G = -G / tau - - Its variable symbol might be replaced by "G__X__spikesEx": - - .. code-block:: - - G__X__spikesEx = -G / tau - - This function updates the right-hand side of `expr` so that it would also read (in this example): - - .. code-block:: - - G__X__spikesEx = -G__X__spikesEx / tau - - These equations will later on be fed to ode-toolbox, so we use the symbol "'" to indicate differential order. - - Note that for kernels/systems of ODE of dimension > 1, all variable orders and all variables for this kernel will already be present in `kernel_buffers`. - """ - for kernel, spike_buf in kernel_buffers: - for kernel_var in kernel.get_variables(): - variable_name_to_replace = kernel_var.get_name() - cls.replace_rhs_variable(expr, variable_name_to_replace=variable_name_to_replace, - kernel_var=kernel_var, spike_buf=spike_buf) - - @classmethod - def is_delta_kernel(cls, kernel: ASTKernel) -> bool: - """ - Catches definition of kernel, or reference (function call or variable name) of a delta kernel function. - """ - if type(kernel) is ASTKernel: - if not len(kernel.get_variables()) == 1: - # delta kernel not allowed if more than one variable is defined in this kernel - return False - expr = kernel.get_expressions()[0] - else: - expr = kernel - - rhs_is_delta_kernel = type(expr) is ASTSimpleExpression \ - and expr.is_function_call() \ - and expr.get_function_call().get_scope().resolve_to_symbol(expr.get_function_call().get_name(), SymbolKind.FUNCTION).equals(PredefinedFunctions.name2function["delta"]) - rhs_is_multiplied_delta_kernel = type(expr) is ASTExpression \ - and type(expr.get_rhs()) is ASTSimpleExpression \ - and expr.get_rhs().is_function_call() \ - and expr.get_rhs().get_function_call().get_scope().resolve_to_symbol(expr.get_rhs().get_function_call().get_name(), SymbolKind.FUNCTION).equals(PredefinedFunctions.name2function["delta"]) - return rhs_is_delta_kernel or rhs_is_multiplied_delta_kernel - @classmethod def get_input_port_by_name(cls, input_blocks: List[ASTInputBlock], port_name: str) -> ASTInputPort: """ @@ -1485,11 +1413,17 @@ def get_input_port_by_name(cls, input_blocks: List[ASTInputBlock], port_name: st size_parameter = input_port.get_size_parameter() if isinstance(size_parameter, ASTSimpleExpression): size_parameter = size_parameter.get_numeric_literal() - port_name, port_index = port_name.split("_") - assert int(port_index) >= 0 - assert int(port_index) <= size_parameter + + if "[" in port_name: + port_name, _port_index = port_name.split("[") + port_index = int(_port_index.strip("]")) + + assert int(port_index) >= 0 + assert int(port_index) <= size_parameter + if input_port.name == port_name: return input_port + return None @classmethod @@ -1732,44 +1666,15 @@ def recursive_necessary_variables_search(cls, vars: List[str], model: ASTModel) return list(set(vars_used)) - @classmethod - def remove_initial_values_for_kernels(cls, model: ASTModel) -> None: - """ - Remove initial values for original declarations (e.g. g_in, g_in', V_m); these might conflict with the initial value expressions returned from ODE-toolbox. - """ - symbols_to_remove = set() - for equations_block in model.get_equations_blocks(): - for kernel in equations_block.get_kernels(): - for kernel_var in kernel.get_variables(): - kernel_var_order = kernel_var.get_differential_order() - for order in range(kernel_var_order): - symbol_name = kernel_var.get_name() + "'" * order - symbols_to_remove.add(symbol_name) - - decl_to_remove = set() - for symbol_name in symbols_to_remove: - for state_block in model.get_state_blocks(): - for decl in state_block.get_declarations(): - if len(decl.get_variables()) == 1: - if decl.get_variables()[0].get_name() == symbol_name: - decl_to_remove.add(decl) - else: - for var in decl.get_variables(): - if var.get_name() == symbol_name: - decl.variables.remove(var) - - for decl in decl_to_remove: - for state_block in model.get_state_blocks(): - if decl in state_block.get_declarations(): - state_block.get_declarations().remove(decl) - @classmethod def update_initial_values_for_odes(cls, model: ASTModel, solver_dicts: List[dict]) -> None: """ - Update initial values for original ODE declarations (e.g. V_m', g_ahp'') that are present in the model - before ODE-toolbox processing, with the formatted variable names and initial values returned by ODE-toolbox. + Update initial values for original ODE declarations (e.g. V_m', g_ahp'') that are present in the model before ODE-toolbox processing, with the formatted variable names and initial values returned by ODE-toolbox. """ from pynestml.utils.model_parser import ModelParser + from pynestml.visitors.ast_parent_visitor import ASTParentVisitor + from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor + assert len(model.get_equations_blocks()) == 1, "Only one equation block should be present" if not model.get_state_blocks(): @@ -1782,10 +1687,6 @@ def update_initial_values_for_odes(cls, model: ASTModel, solver_dicts: List[dict if cls.is_ode_variable(var.get_name(), model): assert cls.variable_in_solver(cls.to_ode_toolbox_processed_name(var_name), solver_dicts) - # replace the left-hand side variable name by the ode-toolbox format - var.set_name(cls.to_ode_toolbox_processed_name(var.get_complete_name())) - var.set_differential_order(0) - # replace the defining expression by the ode-toolbox result iv_expr = cls.get_initial_value_from_ode_toolbox_result( cls.to_ode_toolbox_processed_name(var_name), solver_dicts) @@ -1794,6 +1695,9 @@ def update_initial_values_for_odes(cls, model: ASTModel, solver_dicts: List[dict iv_expr.update_scope(state_block.get_scope()) iv_decl.set_expression(iv_expr) + model.accept(ASTParentVisitor()) + model.accept(ASTSymbolTableVisitor()) + @classmethod def integrate_odes_args_strs_from_function_call(cls, function_call: ASTFunctionCall): arg_names = [] @@ -2018,53 +1922,9 @@ def _visit(self, node): return visitor.calls @classmethod - def create_initial_values_for_kernels(cls, model: ASTModel, solver_dicts: List[Dict], kernels: List[ASTKernel]) -> None: - r""" - Add the variables used in kernels from the ode-toolbox result dictionary as ODEs in NESTML AST - """ - for solver_dict in solver_dicts: - if solver_dict is None: - continue - - for var_name in solver_dict["initial_values"].keys(): - if cls.variable_in_kernels(var_name, kernels): - # original initial value expressions should have been removed to make place for ode-toolbox results - assert not cls.declaration_in_state_block(model, var_name) - - for solver_dict in solver_dicts: - if solver_dict is None: - continue - - for var_name, expr in solver_dict["initial_values"].items(): - # overwrite is allowed because initial values might be repeated between numeric and analytic solver - if cls.variable_in_kernels(var_name, kernels): - spike_in_port_name = var_name.split("__X__")[1] - spike_in_port_name = spike_in_port_name.split("__d")[0] - spike_in_port = ASTUtils.get_input_port_by_name(model.get_input_blocks(), spike_in_port_name) - type_str = "real" - if spike_in_port: - differential_order: int = len(re.findall("__d", var_name)) - if differential_order: - type_str = "(s**-" + str(differential_order) + ")" - - expr = "0 " + type_str # for kernels, "initial value" returned by ode-toolbox is actually the increment value; the actual initial value is 0 (property of the convolution) - if not cls.declaration_in_state_block(model, var_name): - cls.add_declaration_to_state_block(model, var_name, expr, type_str) - - @classmethod - def transform_ode_and_kernels_to_json(cls, model: ASTModel, parameters_blocks: Sequence[ASTBlockWithVariables], - kernel_buffers: Mapping[ASTKernel, ASTInputPort], printer: ASTPrinter) -> Dict: + def transform_odes_to_json(cls, model: ASTModel, parameters_blocks: Sequence[ASTBlockWithVariables], printer: ASTPrinter) -> Dict: """ Converts AST node to a JSON representation suitable for passing to ode-toolbox. - - Each kernel has to be generated for each spike buffer convolve in which it occurs, e.g. if the NESTML model code contains the statements - - .. code-block:: - - convolve(G, exc_spikes) - convolve(G, inh_spikes) - - then `kernel_buffers` will contain the pairs `(G, exc_spikes)` and `(G, inh_spikes)`, from which two ODEs will be generated, with dynamical state (variable) names `G__X__exc_spikes` and `G__X__inh_spikes`. """ odetoolbox_indict = {} @@ -2089,37 +1949,6 @@ def transform_ode_and_kernels_to_json(cls, model: ASTModel, parameters_blocks: S odetoolbox_indict["dynamics"].append(entry) - # write a copy for each (kernel, spike buffer) combination - for kernel, spike_input_port in kernel_buffers: - - if cls.is_delta_kernel(kernel): - # delta function -- skip passing this to ode-toolbox - continue - - for kernel_var in kernel.get_variables(): - expr = cls.get_expr_from_kernel_var(kernel, kernel_var.get_complete_name()) - kernel_order = kernel_var.get_differential_order() - kernel_X_spike_buf_name_ticks = cls.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_input_port, kernel_order, diff_order_symbol="'") - - cls.replace_rhs_variables(expr, kernel_buffers) - - entry = {"expression": kernel_X_spike_buf_name_ticks + " = " + str(expr), "initial_values": {}} - - # initial values need to be declared for order 1 up to kernel order (e.g. none for kernel function - # f(t) = ...; 1 for kernel ODE f'(t) = ...; 2 for f''(t) = ... and so on) - for order in range(kernel_order): - iv_sym_name_ode_toolbox = cls.construct_kernel_X_spike_buf_name( - kernel_var.get_name(), spike_input_port, order, diff_order_symbol="'") - symbol_name_ = kernel_var.get_name() + "'" * order - symbol = equations_block.get_scope().resolve_to_symbol(symbol_name_, SymbolKind.VARIABLE) - assert symbol is not None, "Could not find initial value for variable " + symbol_name_ - initial_value_expr = symbol.get_declaring_expression() - assert initial_value_expr is not None, "No initial value found for variable name " + symbol_name_ - entry["initial_values"][iv_sym_name_ode_toolbox] = printer.print(initial_value_expr) - - odetoolbox_indict["dynamics"].append(entry) - odetoolbox_indict["parameters"] = {} for parameters_block in parameters_blocks: for decl in parameters_block.get_declarations(): @@ -2141,52 +1970,6 @@ def remove_ode_definitions_from_equations_block(cls, model: ASTModel) -> None: for decl in decl_to_remove: equations_block.get_declarations().remove(decl) - @classmethod - def get_delta_factors_(cls, neuron: ASTModel, equations_block: ASTEquationsBlock) -> dict: - r""" - For every occurrence of a convolution of the form `x^(n) = a * convolve(kernel, inport) + ...` where `kernel` is a delta function, add the element `(x^(n), inport) --> a` to the set. - """ - delta_factors = {} - - for ode_eq in equations_block.get_ode_equations(): - var = ode_eq.get_lhs() - expr = ode_eq.get_rhs() - conv_calls = ASTUtils.get_convolve_function_calls(expr) - for conv_call in conv_calls: - assert len( - conv_call.args) == 2, "convolve() function call should have precisely two arguments: kernel and spike input port" - kernel = conv_call.args[0] - if cls.is_delta_kernel(neuron.get_kernel_by_name(kernel.get_variable().get_name())): - inport = conv_call.args[1].get_variable() - expr_str = str(expr) - sympy_expr = sympy.parsing.sympy_parser.parse_expr(expr_str, global_dict=odetoolbox.Shape._sympy_globals) - sympy_expr = sympy.expand(sympy_expr) - sympy_conv_expr = sympy.parsing.sympy_parser.parse_expr(str(conv_call), global_dict=odetoolbox.Shape._sympy_globals) - factor_str = [] - for term in sympy.Add.make_args(sympy_expr): - if term.find(sympy_conv_expr): - factor_str.append(str(term.replace(sympy_conv_expr, 1))) - factor_str = " + ".join(factor_str) - delta_factors[(var, inport)] = factor_str - - return delta_factors - - @classmethod - def remove_kernel_definitions_from_equations_block(cls, model: ASTModel) -> ASTDeclaration: - r""" - Removes all kernels in equations blocks. - """ - for equations_block in model.get_equations_blocks(): - decl_to_remove = set() - for decl in equations_block.get_declarations(): - if type(decl) is ASTKernel: - decl_to_remove.add(decl) - - for decl in decl_to_remove: - equations_block.get_declarations().remove(decl) - - return decl_to_remove - @classmethod def add_timestep_symbol(cls, model: ASTModel) -> None: """ @@ -2199,77 +1982,11 @@ def add_timestep_symbol(cls, model: ASTModel) -> None: )], "\"__h\" is a reserved name, please do not use variables by this name in your NESTML file" model.add_to_internals_block(ModelParser.parse_declaration('__h ms = resolution()'), index=0) - @classmethod - def generate_kernel_buffers(cls, model: ASTModel, equations_block: Union[ASTEquationsBlock, List[ASTEquationsBlock]]) -> Mapping[ASTKernel, ASTInputPort]: - """ - For every occurrence of a convolution of the form `convolve(var, spike_buf)`: add the element `(kernel, spike_buf)` to the set, with `kernel` being the kernel that contains variable `var`. - """ - - kernel_buffers = set() - convolve_calls = ASTUtils.get_convolve_function_calls(equations_block) - for convolve in convolve_calls: - el = (convolve.get_args()[0], convolve.get_args()[1]) - sym = convolve.get_args()[0].get_scope().resolve_to_symbol(convolve.get_args()[0].get_variable().name, SymbolKind.VARIABLE) - if sym is None: - raise Exception("No initial value(s) defined for kernel with variable \"" - + convolve.get_args()[0].get_variable().get_complete_name() + "\"") - if sym.block_type == BlockType.INPUT: - # swap the order - el = (el[1], el[0]) - - # find the corresponding kernel object - var = el[0].get_variable() - assert var is not None - kernel = model.get_kernel_by_name(var.get_name()) - assert kernel is not None, "In convolution \"convolve(" + str(var.name) + ", " + str( - el[1]) + ")\": no kernel by name \"" + var.get_name() + "\" found in model." - - el = (kernel, el[1]) - kernel_buffers.add(el) - - return kernel_buffers - - @classmethod - def replace_convolution_aliasing_inlines(cls, neuron: ASTModel) -> None: - """ - Replace all occurrences of kernel names (e.g. ``I_dend`` and ``I_dend'`` for a definition involving a second-order kernel ``inline kernel I_dend = convolve(kern_name, spike_buf)``) with the ODE-toolbox generated variable ``kern_name__X__spike_buf``. - """ - def replace_var(_expr, replace_var_name: str, replace_with_var_name: str): - if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): - var = _expr.get_variable() - if var.get_name() == replace_var_name: - ast_variable = ASTVariable(replace_with_var_name + '__d' * var.get_differential_order(), - differential_order=0) - ast_variable.set_source_position(var.get_source_position()) - _expr.set_variable(ast_variable) - - elif isinstance(_expr, ASTVariable): - var = _expr - if var.get_name() == replace_var_name: - var.set_name(replace_with_var_name + '__d' * var.get_differential_order()) - var.set_differential_order(0) - - for equation_block in neuron.get_equations_blocks(): - for decl in equation_block.get_declarations(): - if isinstance(decl, ASTInlineExpression): - expr = decl.get_expression() - if isinstance(expr, ASTExpression): - expr = expr.get_lhs() - - if isinstance(expr, ASTSimpleExpression) \ - and '__X__' in str(expr) \ - and expr.get_variable(): - replace_with_var_name = expr.get_variable().get_name() - neuron.accept(ASTHigherOrderVisitor(lambda x: replace_var( - x, decl.get_variable_name(), replace_with_var_name))) - @classmethod def replace_variable_names_in_expressions(cls, model: ASTModel, solver_dicts: List[dict]) -> None: """ Replace all occurrences of variables names in NESTML format (e.g. `g_ex$''`)` with the ode-toolbox formatted variable name (e.g. `g_ex__DOLLAR__d__d`). - - Variables aliasing convolutions should already have been covered by replace_convolution_aliasing_inlines(). """ def replace_var(_expr=None): if isinstance(_expr, ASTSimpleExpression) and _expr.is_variable(): @@ -2294,8 +2011,9 @@ def func(x): @classmethod def replace_convolve_calls_with_buffers_(cls, model: ASTModel, equations_block: ASTEquationsBlock) -> None: r""" - Replace all occurrences of `convolve(kernel[']^n, spike_input_port)` with the corresponding buffer variable, e.g. `g_E__X__spikes_exc[__d]^n` for a kernel named `g_E` and a spike input port named `spikes_exc`. + Replace all occurrences of `convolve(kernel[']^n, spike_input_port)` with the corresponding buffer variable, e.g. `g_E__conv__spikes_exc[__d]^n` for a kernel named `g_E` and a spike input port named `spikes_exc`. """ + from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor def replace_function_call_through_var(_expr=None): if _expr.is_function_call() and _expr.get_function_call().get_name() == "convolve": @@ -2326,6 +2044,7 @@ def func(x): return replace_function_call_through_var(x) if isinstance(x, ASTSimpleExpression) else True equations_block.accept(ASTHigherOrderVisitor(func)) + equations_block.accept(ASTSymbolTableVisitor()) @classmethod def update_blocktype_for_common_parameters(cls, node): @@ -2519,13 +2238,6 @@ def visit_variable(self, node): for expr in numeric_update_expressions.values(): expr.accept(visitor) - for update_expr_list in neuron.spike_updates.values(): - for update_expr in update_expr_list: - update_expr.accept(visitor) - - for update_expr in neuron.post_spike_updates.values(): - update_expr.accept(visitor) - for node in neuron.equations_with_delay_vars + neuron.equations_with_vector_vars: node.accept(visitor) diff --git a/pynestml/utils/logger.py b/pynestml/utils/logger.py index 06e95b804..8404f1245 100644 --- a/pynestml/utils/logger.py +++ b/pynestml/utils/logger.py @@ -19,7 +19,7 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from typing import List, Mapping, Optional, Tuple +from typing import List, Mapping, Optional, Tuple, Union from collections import OrderedDict from enum import Enum @@ -75,6 +75,7 @@ class Logger: def init_logger(cls, logging_level: LoggingLevel): """ Initializes the logger. + :param logging_level: the logging level as required :type logging_level: LoggingLevel """ @@ -82,7 +83,6 @@ def init_logger(cls, logging_level: LoggingLevel): cls.curr_message = 0 cls.log = {} cls.log_frozen = False - return @classmethod def freeze_log(cls, do_freeze: bool = True): @@ -95,6 +95,7 @@ def freeze_log(cls, do_freeze: bool = True): def get_log(cls) -> Mapping[int, Tuple[ASTNode, LoggingLevel, str]]: """ Returns the overall log of messages. The structure of the log is: (NODE, LEVEL, MESSAGE) + :return: mapping from id to ASTNode, log level and message. """ return cls.log @@ -103,6 +104,7 @@ def get_log(cls) -> Mapping[int, Tuple[ASTNode, LoggingLevel, str]]: def set_log(cls, log, counter): """ Restores log from the 'log' variable + :param log: the log :param counter: the counter """ @@ -113,20 +115,19 @@ def set_log(cls, log, counter): def log_message(cls, node: ASTNode = None, code: MessageCode = None, message: str = None, error_position: ASTSourceLocation = None, log_level: LoggingLevel = None): """ Logs the handed over message on the handed over node. If the current logging is appropriate, the message is also printed. + :param node: the node in which the error occurred :param code: a single error code - :type code: ErrorCode :param error_position: the position on which the error occurred. - :type error_position: SourcePosition :param message: a message. - :type message: str :param log_level: the corresponding log level. - :type log_level: LoggingLevel """ if cls.log_frozen: return + if cls.curr_message is None: cls.init_logger(LoggingLevel.INFO) + from pynestml.meta_model.ast_node import ASTNode from pynestml.utils.ast_source_location import ASTSourceLocation assert (node is None or isinstance(node, ASTNode)), \ @@ -134,15 +135,23 @@ def log_message(cls, node: ASTNode = None, code: MessageCode = None, message: st assert (error_position is None or isinstance(error_position, ASTSourceLocation)), \ '(PyNestML.Logger) Wrong type of error position provided (%s)!' % type(error_position) from pynestml.meta_model.ast_model import ASTModel + if isinstance(node, ASTModel): cls.log[cls.curr_message] = ( node.get_artifact_name(), node, log_level, code, error_position, message) - elif cls.current_node is not None: - cls.log[cls.curr_message] = (cls.current_node.get_artifact_name(), cls.current_node, + else: + if cls.current_node is not None: + artifact_name = cls.current_node.get_artifact_name() + else: + artifact_name = "" + + cls.log[cls.curr_message] = (artifact_name, cls.current_node, log_level, code, error_position, message) + cls.curr_message += 1 if cls.no_print: return + if cls.logging_level.value <= log_level.value: if isinstance(node, ASTInlineExpression): node_name = node.variable_name @@ -163,10 +172,9 @@ def log_message(cls, node: ASTNode = None, code: MessageCode = None, message: st def string_to_level(cls, string: str) -> LoggingLevel: """ Returns the logging level corresponding to the handed over string. If no such exits, returns None. + :param string: a single string representing the level. - :type string: str :return: a single logging level. - :rtype: LoggingLevel """ if string == 'DEBUG': return LoggingLevel.DEBUG @@ -183,7 +191,7 @@ def string_to_level(cls, string: str) -> LoggingLevel: if string == 'NO' or string == 'NONE': return LoggingLevel.NO - raise Exception('Tried to convert unknown string \"' + string + '\" to logging level') + raise Exception("Tried to convert unknown string '" + string + "' to logging level") @classmethod def level_to_string(cls, level: LoggingLevel) -> str: @@ -207,7 +215,7 @@ def level_to_string(cls, level: LoggingLevel) -> str: if level == LoggingLevel.NO: return 'NO' - raise Exception('Tried to convert unknown logging level \"' + str(level) + '\" to string') + raise Exception("Tried to convert unknown logging level '" + str(level) + "' to string") @classmethod def set_logging_level(cls, level: LoggingLevel) -> None: @@ -218,79 +226,89 @@ def set_logging_level(cls, level: LoggingLevel) -> None: """ if cls.log_frozen: return + cls.logging_level = level @classmethod def set_current_node(cls, node: Optional[ASTNode]) -> None: """ - Sets the handed over node as the currently processed one. This enables a retrieval of messages for a - specific node. - :param node: a single node instance + Sets the handed over node as the currently processed one. This enables a retrieval of messages for a specific node. + + :param node: a single node instance """ cls.current_node = node @classmethod - def get_all_messages_of_level_and_or_node(cls, node: ASTNode, level: LoggingLevel) -> List[Tuple[ASTNode, LoggingLevel, str]]: + def get_all_messages_of_level_and_or_node(cls, node: Union[ASTNode, str], level: LoggingLevel) -> List[Tuple[ASTNode, LoggingLevel, str]]: """ - Returns all messages which have a certain logging level, or have been reported for a certain node, or - both. + Returns all messages which have a certain logging level, or have been reported for a certain node, or both. + :param node: a single node instance :param level: a logging level - :type level: LoggingLevel :return: a list of messages with their levels. - :rtype: list((str,Logging_Level) """ if level is None and node is None: return cls.get_log() + + if isinstance(node, str): + # search by artifact name + node_artifact_name = node + node = None + else: + # search by artifact class object + node_artifact_name = None + ret = list() for (artifactName, node_i, logLevel, code, errorPosition, message) in cls.log.values(): - if (level == logLevel if level is not None else True) and ( - node if node is not None else True) and ( - node.get_artifact_name() == artifactName if node is not None else True): + if (level == logLevel if level is not None else True) and (node if node is not None else True) and (node_artifact_name == artifactName if node is not None else True): ret.append((node, logLevel, message)) + return ret @classmethod def get_all_messages_of_level(cls, level: LoggingLevel) -> List[Tuple[ASTNode, LoggingLevel, str]]: """ Returns all messages which have a certain logging level. + :param level: a logging level - :type level: LoggingLevel :return: a list of messages with their levels. - :rtype: list((str,Logging_Level) """ if level is None: return cls.get_log() + ret = list() for (artifactName, node, logLevel, code, errorPosition, message) in cls.log.values(): if level == logLevel: ret.append((node, logLevel, message)) + return ret @classmethod def get_all_messages_of_node(cls, node: ASTNode) -> List[Tuple[ASTNode, LoggingLevel, str]]: """ Returns all messages which have been reported for a certain node. + :param node: a single node instance :return: a list of messages with their levels. - :rtype: list((str,Logging_Level) """ if node is None: return cls.get_log() + ret = list() for (artifactName, node_i, logLevel, code, errorPosition, message) in cls.log.values(): if (node_i == node if node is not None else True) and \ (node.get_artifact_name() == artifactName if node is not None else True): ret.append((node, logLevel, message)) + return ret @classmethod def has_errors(cls, node: ASTNode) -> bool: """ Indicates whether the handed over node, thus the corresponding model, has errors. + :param node: a single node instance. :return: True if errors detected, otherwise False - :rtype: bool """ return len(cls.get_all_messages_of_level_and_or_node(node, LoggingLevel.ERROR)) > 0 @@ -311,6 +329,7 @@ def get_json_format(cls) -> str: (node.get_name() if node is not None else 'GLOBAL') + '", ' + \ '"severity":"' \ + str(logLevel.name) + '", ' + if code is not None: ret += '"code":"' + \ code.name + \ @@ -323,10 +342,12 @@ def get_json_format(cls) -> str: '", ' + \ '"message":"' + str(message).replace('"', "'") + '"}' ret += ',' + if len(cls.log.keys()) == 0: parsed = json.loads('[]', object_pairs_hook=OrderedDict) else: ret = ret[:-1] # delete the last "," ret += ']' parsed = json.loads(ret, object_pairs_hook=OrderedDict) + return json.dumps(parsed, indent=2, sort_keys=False) diff --git a/pynestml/utils/mechs_info_enricher.py b/pynestml/utils/mechs_info_enricher.py index 456ece178..ea645a02c 100644 --- a/pynestml/utils/mechs_info_enricher.py +++ b/pynestml/utils/mechs_info_enricher.py @@ -22,13 +22,14 @@ from collections import defaultdict from pynestml.meta_model.ast_model import ASTModel +from pynestml.symbols.predefined_functions import PredefinedFunctions +from pynestml.symbols.symbol import SymbolKind +from pynestml.utils.ast_vector_parameter_setter_and_printer_factory import ASTVectorParameterSetterAndPrinterFactory from pynestml.visitors.ast_parent_visitor import ASTParentVisitor from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor from pynestml.utils.ast_utils import ASTUtils -from pynestml.visitors.ast_visitor import ASTVisitor from pynestml.utils.model_parser import ModelParser -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.symbol import SymbolKind +from pynestml.visitors.ast_visitor import ASTVisitor class MechsInfoEnricher: @@ -57,33 +58,6 @@ def transform_ode_solutions(cls, neuron, mechs_info): solution_transformed["states"] = defaultdict() solution_transformed["propagators"] = defaultdict() - for variable_name, rhs_str in ode_info["ode_toolbox_output"][ode_solution_index]["initial_values"].items(): - variable = neuron.get_equations_blocks()[0].get_scope().resolve_to_symbol(variable_name, - SymbolKind.VARIABLE) - - expression = ModelParser.parse_expression(rhs_str) - # pretend that update expressions are in "equations" block, - # which should always be present, as synapses have been - # defined to get here - expression.update_scope(neuron.get_equations_blocks()[0].get_scope()) - expression.accept(ASTSymbolTableVisitor()) - - update_expr_str = ode_info["ode_toolbox_output"][ode_solution_index]["update_expressions"][ - variable_name] - update_expr_ast = ModelParser.parse_expression( - update_expr_str) - # pretend that update expressions are in "equations" block, - # which should always be present, as differential equations - # must have been defined to get here - update_expr_ast.update_scope( - neuron.get_equations_blocks()[0].get_scope()) - update_expr_ast.accept(ASTSymbolTableVisitor()) - - solution_transformed["states"][variable_name] = { - "ASTVariable": variable, - "init_expression": expression, - "update_expression": update_expr_ast, - } for variable_name, rhs_str in ode_info["ode_toolbox_output"][ode_solution_index]["propagators"].items(): prop_variable = neuron.get_equations_blocks()[0].get_scope().resolve_to_symbol(variable_name, SymbolKind.VARIABLE) @@ -118,6 +92,36 @@ def transform_ode_solutions(cls, neuron, mechs_info): PredefinedFunctions.TIME_RESOLUTION: mechanism_info["time_resolution_var"] = variable + for variable_name, rhs_str in ode_info["ode_toolbox_output"][ode_solution_index]["initial_values"].items(): + variable = neuron.get_equations_blocks()[0].get_scope().resolve_to_symbol(variable_name, + SymbolKind.VARIABLE) + + expression = ModelParser.parse_expression(rhs_str) + # pretend that update expressions are in "equations" block, + # which should always be present, as synapses have been + # defined to get here + expression.update_scope(neuron.get_equations_blocks()[0].get_scope()) + expression.accept(ASTSymbolTableVisitor()) + + update_expr_str = ode_info["ode_toolbox_output"][ode_solution_index]["update_expressions"][ + variable_name] + update_expr_ast = ModelParser.parse_expression( + update_expr_str) + # pretend that update expressions are in "equations" block, + # which should always be present, as differential equations + # must have been defined to get here + update_expr_ast.update_scope( + neuron.get_scope()) + update_expr_ast.accept(ASTParentVisitor()) + update_expr_ast.accept(ASTSymbolTableVisitor()) + neuron.accept(ASTSymbolTableVisitor()) + + solution_transformed["states"][variable_name] = { + "ASTVariable": variable, + "init_expression": expression, + "update_expression": update_expr_ast, + } + mechanism_info["ODEs"][ode_var_name]["transformed_solutions"].append(solution_transformed) neuron.accept(ASTParentVisitor()) diff --git a/pynestml/utils/messages.py b/pynestml/utils/messages.py index 69b32a8f4..c21ea47a7 100644 --- a/pynestml/utils/messages.py +++ b/pynestml/utils/messages.py @@ -18,11 +18,15 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from enum import Enum + +from __future__ import annotations + from typing import Tuple -from pynestml.meta_model.ast_inline_expression import ASTInlineExpression from collections.abc import Iterable +from enum import Enum + +from pynestml.meta_model.ast_inline_expression import ASTInlineExpression from pynestml.meta_model.ast_function import ASTFunction @@ -158,8 +162,8 @@ def get_input_path_not_found(cls, path): return MessageCode.INPUT_PATH_NOT_FOUND, message @classmethod - def get_unknown_target(cls, target): - message = 'Unknown target ("%s")' % (target) + def get_unknown_target_platform(cls, target: str): + message = "Unknown target: '" + target + "'" return MessageCode.UNKNOWN_TARGET, message @classmethod @@ -313,22 +317,13 @@ def get_different_type_rhs_lhs( return MessageCode.CAST_NOT_POSSIBLE, message @classmethod - def get_type_different_from_expected(cls, expected_type, got_type): + def get_type_different_from_expected(cls, expected_type, got_type) -> Tuple[MessageCode, str]: """ Returns a message indicating that the received type is different from the expected one. :param expected_type: the expected type - :type expected_type: TypeSymbol :param got_type: the actual type - :type got_type: type_symbol :return: a message - :rtype: (MessageCode,str) """ - from pynestml.symbols.type_symbol import TypeSymbol - assert (expected_type is not None and isinstance(expected_type, TypeSymbol)), \ - '(PyNestML.Utils.Message) Not a type symbol provided (%s)!' % type( - expected_type) - assert (got_type is not None and isinstance(got_type, TypeSymbol)), \ - '(PyNestML.Utils.Message) Not a type symbol provided (%s)!' % type(got_type) message = 'Actual type different from expected. Expected: \'%s\', got: \'%s\'!' % ( expected_type.print_symbol(), got_type.print_symbol()) return MessageCode.TYPE_DIFFERENT_FROM_EXPECTED, message @@ -389,8 +384,7 @@ def get_model_contains_errors( return MessageCode.MODEL_CONTAINS_ERRORS, message @classmethod - def get_start_processing_model( - cls, model_name: str) -> Tuple[MessageCode, str]: + def get_start_processing_model(cls, model_name: str) -> Tuple[MessageCode, str]: """ Returns a message indicating that the processing of a model is started. :param model_name: the name of the model @@ -398,7 +392,7 @@ def get_start_processing_model( """ assert (model_name is not None and isinstance(model_name, str)), \ '(PyNestML.Utils.Message) Not a string provided (%s)!' % type(model_name) - message = 'Starts processing of the model \'' + model_name + '\'' + message = 'Starting processing of the model \'' + model_name + '\'' return MessageCode.START_PROCESSING_MODEL, message @classmethod @@ -430,11 +424,10 @@ def get_module_generated(cls, path: str) -> Tuple[MessageCode, str]: return MessageCode.MODULE_SUCCESSFULLY_GENERATED, message @classmethod - def get_variable_used_before_declaration(cls, variable_name): + def get_variable_used_before_declaration(cls, variable_name: str): """ Returns a message indicating that a variable is used before declaration. :param variable_name: a variable name - :type variable_name: str :return: a message :rtype: (MessageCode,str) """ @@ -701,7 +694,7 @@ def get_model_redeclared(cls, name: str) -> Tuple[MessageCode, str]: '(PyNestML.Utils.Message) Not a string provided (%s)!' % type(name) assert (name is not None and isinstance(name, str)), \ '(PyNestML.Utils.Message) Not a string provided (%s)!' % type(name) - message = 'model \'%s\' redeclared!' % name + message = 'Model \'%s\' redeclared!' % name return MessageCode.MODEL_REDECLARED, message @classmethod diff --git a/pynestml/utils/model_parser.py b/pynestml/utils/model_parser.py index 7fabf361e..62a8669bb 100644 --- a/pynestml/utils/model_parser.py +++ b/pynestml/utils/model_parser.py @@ -24,6 +24,7 @@ from antlr4 import CommonTokenStream, FileStream, InputStream from antlr4.error.ErrorStrategy import BailErrorStrategy, DefaultErrorStrategy from antlr4.error.ErrorListener import ConsoleErrorListener +from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.generated.PyNestMLLexer import PyNestMLLexer from pynestml.generated.PyNestMLParser import PyNestMLParser @@ -65,6 +66,7 @@ from pynestml.meta_model.ast_variable import ASTVariable from pynestml.meta_model.ast_while_stmt import ASTWhileStmt from pynestml.symbol_table.symbol_table import SymbolTable +from pynestml.transformers.assign_implicit_conversion_factors_transformer import AssignImplicitConversionFactorsTransformer from pynestml.utils.ast_source_location import ASTSourceLocation from pynestml.utils.error_listener import NestMLErrorListener from pynestml.utils.logger import Logger, LoggingLevel @@ -142,10 +144,14 @@ def parse_file(cls, file_path=None): for model in ast.get_model_list(): model.accept(ASTSymbolTableVisitor()) SymbolTable.add_model_scope(model.get_name(), model.get_scope()) + Logger.set_current_node(model) + AssignImplicitConversionFactorsTransformer().transform(model) + Logger.set_current_node(None) # store source paths for model in ast.get_model_list(): model.file_path = file_path + ast.file_path = file_path return ast diff --git a/pynestml/utils/ode_toolbox_utils.py b/pynestml/utils/ode_toolbox_utils.py index a4162a4d0..19f290d81 100644 --- a/pynestml/utils/ode_toolbox_utils.py +++ b/pynestml/utils/ode_toolbox_utils.py @@ -41,7 +41,7 @@ def _rewrite_piecewise_into_ternary(cls, s: str) -> str: sympy_expr = sympy.parsing.sympy_parser.parse_expr(s, global_dict=_sympy_globals_no_functions) class MySympyPrinter(StrPrinter): - """Resulting expressions will be parsed by NESTML parser. R + """Resulting expressions will be parsed by NESTML parser. """ def _print_Function(self, expr): if expr.func.__name__ == "Piecewise": diff --git a/pynestml/utils/synapse_processing.py b/pynestml/utils/synapse_processing.py index 464abd269..08ba6a2a0 100644 --- a/pynestml/utils/synapse_processing.py +++ b/pynestml/utils/synapse_processing.py @@ -178,7 +178,7 @@ def transform_ode_and_kernels_to_json( convolve(G, ex_spikes) convolve(G, in_spikes) - then `kernel_buffers` will contain the pairs `(G, ex_spikes)` and `(G, in_spikes)`, from which two ODEs will be generated, with dynamical state (variable) names `G__X__ex_spikes` and `G__X__in_spikes`. + then `kernel_buffers` will contain the pairs `(G, ex_spikes)` and `(G, in_spikes)`, from which two ODEs will be generated, with dynamical state (variable) names `G__conv__ex_spikes` and `G__conv__in_spikes`. :param parameters_block: ASTBlockWithVariables :return: Dict diff --git a/pynestml/utils/type_caster.py b/pynestml/utils/type_caster.py index 34e4e6ccc..4ce2624dd 100644 --- a/pynestml/utils/type_caster.py +++ b/pynestml/utils/type_caster.py @@ -28,12 +28,11 @@ class TypeCaster: @staticmethod def do_magnitude_conversion_rhs_to_lhs(_rhs_type_symbol, _lhs_type_symbol, _containing_expression): """ - determine conversion factor from rhs to lhs, register it with the relevant expression + Determine conversion factor from rhs to lhs, register it with the relevant expression """ _containing_expression.set_implicit_conversion_factor( - UnitTypeSymbol.get_conversion_factor(_lhs_type_symbol.astropy_unit, - _rhs_type_symbol.astropy_unit)) - _containing_expression.type = _lhs_type_symbol + UnitTypeSymbol.get_conversion_factor(_rhs_type_symbol.astropy_unit, + _lhs_type_symbol.astropy_unit)) code, message = Messages.get_implicit_magnitude_conversion(_lhs_type_symbol, _rhs_type_symbol, _containing_expression.get_implicit_conversion_factor()) Logger.log_message(code=code, message=message, @@ -45,18 +44,26 @@ def try_to_recover_or_error(_lhs_type_symbol, _rhs_type_symbol, _containing_expr if _rhs_type_symbol.is_castable_to(_lhs_type_symbol): if isinstance(_lhs_type_symbol, UnitTypeSymbol) \ and isinstance(_rhs_type_symbol, UnitTypeSymbol): - conversion_factor = UnitTypeSymbol.get_conversion_factor( - _lhs_type_symbol.astropy_unit, _rhs_type_symbol.astropy_unit) + conversion_factor = UnitTypeSymbol.get_conversion_factor(_rhs_type_symbol.astropy_unit, _lhs_type_symbol.astropy_unit) + + if conversion_factor is None: + # error during conversion + code, message = Messages.get_type_different_from_expected(_lhs_type_symbol, _rhs_type_symbol) + Logger.log_message(error_position=_containing_expression.get_source_position(), + code=code, message=message, log_level=LoggingLevel.ERROR) + return + if not conversion_factor == 1.: # the units are mutually convertible, but require a factor unequal to 1 (e.g. mV and A*Ohm) - TypeCaster.do_magnitude_conversion_rhs_to_lhs( - _rhs_type_symbol, _lhs_type_symbol, _containing_expression) + TypeCaster.do_magnitude_conversion_rhs_to_lhs(_rhs_type_symbol, _lhs_type_symbol, _containing_expression) + # the units are mutually convertible (e.g. V and A*Ohm) code, message = Messages.get_implicit_cast_rhs_to_lhs(_rhs_type_symbol.print_symbol(), _lhs_type_symbol.print_symbol()) Logger.log_message(error_position=_containing_expression.get_source_position(), code=code, message=message, log_level=LoggingLevel.INFO) - else: - code, message = Messages.get_type_different_from_expected(_lhs_type_symbol, _rhs_type_symbol) - Logger.log_message(error_position=_containing_expression.get_source_position(), - code=code, message=message, log_level=LoggingLevel.ERROR) + return + + code, message = Messages.get_type_different_from_expected(_lhs_type_symbol, _rhs_type_symbol) + Logger.log_message(error_position=_containing_expression.get_source_position(), + code=code, message=message, log_level=LoggingLevel.ERROR) diff --git a/pynestml/visitors/ast_builder_visitor.py b/pynestml/visitors/ast_builder_visitor.py index 0e766d530..bfc4dd902 100644 --- a/pynestml/visitors/ast_builder_visitor.py +++ b/pynestml/visitors/ast_builder_visitor.py @@ -52,16 +52,17 @@ def visitNestMLCompilationUnit(self, ctx): models = list() for child in ctx.model(): models.append(self.visit(child)) + # extract the name of the artifact from the context if hasattr(ctx.start.source[1], 'fileName'): artifact_name = ntpath.basename(ctx.start.source[1].fileName) else: artifact_name = 'parsed_from_string' + compilation_unit = ASTNodeFactory.create_ast_nestml_compilation_unit(list_of_models=models, source_position=create_source_pos(ctx), artifact_name=artifact_name) - # first ensure certain properties of the model - CoCosManager.check_model_names_unique(compilation_unit) + return compilation_unit # Visit a parse tree produced by PyNESTMLParser#datatype. @@ -387,15 +388,6 @@ def visitDeclaration(self, ctx): expression = self.visit(ctx.rhs) if ctx.rhs is not None else None invariant = self.visit(ctx.invariant) if ctx.invariant is not None else None - # print("Visiting variable \"" + str(str(ctx.NAME())) + "\"...") - # # check if this variable was decorated as homogeneous - # import pynestml.generated.PyNestMLLexer - # is_homogeneous = any([isinstance(ch, pynestml.generated.PyNestMLParser.PyNestMLParser.AnyDecoratorContext) \ - # and len(ch.getTokens(pynestml.generated.PyNestMLLexer.PyNestMLLexer.DECORATOR_HOMOGENEOUS)) > 0 \ - # for ch in ctx.parentCtx.children]) - # if is_homogeneous: - # print("\t----> is homogeneous") - declaration = ASTNodeFactory.create_ast_declaration(is_recordable=is_recordable, variables=variables, data_type=data_type, diff --git a/pynestml/visitors/ast_function_call_visitor.py b/pynestml/visitors/ast_function_call_visitor.py index 7d7bf75c4..e4ec8650e 100644 --- a/pynestml/visitors/ast_function_call_visitor.py +++ b/pynestml/visitors/ast_function_call_visitor.py @@ -94,7 +94,6 @@ def visit_simple_expression(self, node: ASTSimpleExpression) -> None: # return type of the convolve function is the type of the second parameter multiplied by the unit of time (s) if function_name == PredefinedFunctions.CONVOLVE: - # Deviations from the assumptions made here are handled in the convolveCoco buffer_parameter = node.get_function_call().get_args()[1] if buffer_parameter.get_variable() is not None: diff --git a/pynestml/visitors/ast_symbol_table_visitor.py b/pynestml/visitors/ast_symbol_table_visitor.py index 011182543..bc85d4cdd 100644 --- a/pynestml/visitors/ast_symbol_table_visitor.py +++ b/pynestml/visitors/ast_symbol_table_visitor.py @@ -19,7 +19,6 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . -from pynestml.cocos.co_cos_manager import CoCosManager from pynestml.meta_model.ast_model import ASTModel from pynestml.meta_model.ast_model_body import ASTModelBody from pynestml.meta_model.ast_namespace_decorator import ASTNamespaceDecorator @@ -53,7 +52,6 @@ def __init__(self): self.symbol_stack = Stack() self.scope_stack = Stack() self.block_type_stack = Stack() - self.after_ast_rewrite_ = False def visit_model(self, node: ASTModel) -> None: """ @@ -79,10 +77,6 @@ def visit_model(self, node: ASTModel) -> None: node.get_scope().add_symbol(types[symbol]) def endvisit_model(self, node: ASTModel): - # before following checks occur, we need to ensure several simple properties - CoCosManager.post_symbol_table_builder_checks( - node, after_ast_rewrite=self.after_ast_rewrite_) - # update the equations for equation_block in node.get_equations_blocks(): ASTUtils.assign_ode_to_variables(equation_block) @@ -287,8 +281,7 @@ def visit_declaration(self, node: ASTDeclaration) -> None: namespace_decorators = {} for d in node.get_decorators(): if isinstance(d, ASTNamespaceDecorator): - namespace_decorators[str(d.get_namespace())] = str( - d.get_name()) + namespace_decorators[str(d.get_namespace())] = str(d.get_name()) else: decorators.append(d) @@ -296,6 +289,7 @@ def visit_declaration(self, node: ASTDeclaration) -> None: block_type = None if not self.block_type_stack.is_empty(): block_type = self.block_type_stack.top() + for var in node.get_variables(): # for all variables declared create a new symbol var.update_scope(node.get_scope()) @@ -324,11 +318,14 @@ def visit_declaration(self, node: ASTDeclaration) -> None: symbol.set_comment(node.get_comment()) node.get_scope().add_symbol(symbol) var.set_type_symbol(type_symbol) + # the data type node.get_data_type().update_scope(node.get_scope()) + # the rhs update if node.has_expression(): node.get_expression().update_scope(node.get_scope()) + # the invariant update if node.has_invariant(): node.get_invariant().update_scope(node.get_scope()) diff --git a/tests/cocos_test.py b/tests/cocos_test.py deleted file mode 100644 index f557faaf0..000000000 --- a/tests/cocos_test.py +++ /dev/null @@ -1,698 +0,0 @@ -# -*- coding: utf-8 -*- -# -# cocos_test.py -# -# This file is part of NEST. -# -# Copyright (C) 2004 The NEST Initiative -# -# NEST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# NEST is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with NEST. If not, see . - -from __future__ import print_function - -import os -import unittest - -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.symbol_table.symbol_table import SymbolTable -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.predefined_types import PredefinedTypes -from pynestml.symbols.predefined_units import PredefinedUnits -from pynestml.symbols.predefined_variables import PredefinedVariables -from pynestml.utils.logger import LoggingLevel, Logger -from pynestml.utils.model_parser import ModelParser - - -class CoCosTest(unittest.TestCase): - - def setUp(self): - Logger.init_logger(LoggingLevel.INFO) - SymbolTable.initialize_symbol_table( - ASTSourceLocation( - start_line=0, - start_column=0, - end_line=0, - end_column=0)) - PredefinedUnits.register_units() - PredefinedTypes.register_types() - PredefinedVariables.register_variables() - PredefinedFunctions.register_functions() - - def test_invalid_element_defined_after_usage(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVariableDefinedAfterUsage.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_element_defined_after_usage(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVariableDefinedAfterUsage.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_element_in_same_line(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoElementInSameLine.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_element_in_same_line(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoElementInSameLine.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_integrate_odes_called_if_equations_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoIntegrateOdesCalledIfEquationsDefined.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_integrate_odes_called_if_equations_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoIntegrateOdesCalledIfEquationsDefined.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_element_not_defined_in_scope(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVariableNotDefined.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 5) - - def test_valid_element_not_defined_in_scope(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVariableNotDefined.nestml')) - self.assertEqual( - len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), - 0) - - def test_variable_with_same_name_as_unit(self): - Logger.set_logging_level(LoggingLevel.NO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVariableWithSameNameAsUnit.nestml')) - self.assertEqual( - len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.WARNING)), - 3) - - def test_invalid_variable_redeclaration(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVariableRedeclared.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_variable_redeclaration(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVariableRedeclared.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_each_block_unique(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoEachBlockUnique.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_valid_each_block_unique(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoEachBlockUnique.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_function_unique_and_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoFunctionNotUnique.nestml')) - self.assertEqual( - len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 5) - - def test_valid_function_unique_and_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoFunctionNotUnique.nestml')) - self.assertEqual( - len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_inline_expressions_have_rhs(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInlineExpressionHasNoRhs.nestml')) - assert model is None - - def test_valid_inline_expressions_have_rhs(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInlineExpressionHasNoRhs.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_inline_expression_has_several_lhs(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInlineExpressionWithSeveralLhs.nestml')) - assert model is None - - def test_valid_inline_expression_has_several_lhs(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInlineExpressionWithSeveralLhs.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_no_values_assigned_to_input_ports(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoValueAssignedToInputPort.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_no_values_assigned_to_input_ports(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoValueAssignedToInputPort.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_order_of_equations_correct(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoNoOrderOfEquations.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_valid_order_of_equations_correct(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoNoOrderOfEquations.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_numerator_of_unit_one(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoUnitNumeratorNotOne.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 2) - - def test_valid_numerator_of_unit_one(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoUnitNumeratorNotOne.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_names_of_neurons_unique(self): - Logger.init_logger(LoggingLevel.INFO) - ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoMultipleNeuronsWithEqualName.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(None, LoggingLevel.ERROR)), 1) - - def test_valid_names_of_neurons_unique(self): - Logger.init_logger(LoggingLevel.INFO) - ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoMultipleNeuronsWithEqualName.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(None, LoggingLevel.ERROR)), 0) - - def test_invalid_no_nest_collision(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoNestNamespaceCollision.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_no_nest_collision(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoNestNamespaceCollision.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_redundant_input_port_keywords_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInputPortWithRedundantTypes.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_redundant_input_port_keywords_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInputPortWithRedundantTypes.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_parameters_assigned_only_in_parameters_block(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoParameterAssignedOutsideBlock.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_parameters_assigned_only_in_parameters_block(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoParameterAssignedOutsideBlock.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_inline_expressions_assigned_only_in_declaration(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoAssignmentToInlineExpression.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_invalid_internals_assigned_only_in_internals_block(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInternalAssignedOutsideBlock.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_internals_assigned_only_in_internals_block(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInternalAssignedOutsideBlock.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_function_with_wrong_arg_number_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoFunctionCallNotConsistentWrongArgNumber.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_function_with_wrong_arg_number_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoFunctionCallNotConsistentWrongArgNumber.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_init_values_have_rhs_and_ode(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInitValuesWithoutOde.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.WARNING)), 2) - - def test_valid_init_values_have_rhs_and_ode(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInitValuesWithoutOde.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.WARNING)), 2) - - def test_invalid_incorrect_return_stmt_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoIncorrectReturnStatement.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 4) - - def test_valid_incorrect_return_stmt_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoIncorrectReturnStatement.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_ode_vars_outside_init_block_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoOdeVarNotInInitialValues.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_ode_vars_outside_init_block_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoOdeVarNotInInitialValues.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_convolve_correctly_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoConvolveNotCorrectlyProvided.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 3) - - def test_valid_convolve_correctly_defined(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoConvolveNotCorrectlyProvided.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_vector_in_non_vector_declaration_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVectorInNonVectorDeclaration.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_vector_in_non_vector_declaration_detected(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVectorInNonVectorDeclaration.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_vector_parameter_declaration(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVectorParameterDeclaration.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_vector_parameter_declaration(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVectorParameterDeclaration.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_vector_parameter_type(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVectorParameterType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_vector_parameter_type(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVectorParameterType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_vector_parameter_size(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVectorDeclarationSize.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_valid_vector_parameter_size(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVectorDeclarationSize.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_convolve_correctly_parameterized(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoConvolveNotCorrectlyParametrized.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_valid_convolve_correctly_parameterized(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoConvolveNotCorrectlyParametrized.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 0) - - def test_invalid_invariant_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoInvariantNotBool.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_invariant_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoInvariantNotBool.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_expression_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoIllegalExpression.nestml')) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 6) - - def test_valid_expression_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoIllegalExpression.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_compound_expression_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CompoundOperatorWithDifferentButCompatibleUnits.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 5) - - def test_valid_compound_expression_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CompoundOperatorWithDifferentButCompatibleUnits.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_ode_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoOdeIncorrectlyTyped.nestml')) - self.assertTrue(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)) > 0) - - def test_valid_ode_correctly_typed(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoOdeCorrectlyTyped.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_output_block_defined_if_emit_call(self): - """test that an error is raised when the emit_spike() function is called by the neuron, but an output block is not defined""" - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoOutputPortDefinedIfEmitCall.nestml')) - self.assertTrue(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)) > 0) - - def test_invalid_output_port_defined_if_emit_call(self): - """test that an error is raised when the emit_spike() function is called by the neuron, but a spiking output port is not defined""" - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoOutputPortDefinedIfEmitCall-2.nestml')) - self.assertTrue(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)) > 0) - - def test_valid_output_port_defined_if_emit_call(self): - """test that no error is raised when the output block is missing, but not emit_spike() functions are called""" - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoOutputPortDefinedIfEmitCall.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_valid_coco_kernel_type(self): - """ - Test the functionality of CoCoKernelType. - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoKernelType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_coco_kernel_type(self): - """ - Test the functionality of CoCoKernelType. - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoKernelType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_invalid_coco_kernel_type_initial_values(self): - """ - Test the functionality of CoCoKernelType. - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoKernelTypeInitialValues.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 4) - - def test_valid_coco_state_variables_initialized(self): - """ - Test that the CoCo condition is applicable for all the variables in the state block initialized with a value - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoStateVariablesInitialized.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_coco_state_variables_initialized(self): - """ - Test that the CoCo condition is applicable for all the variables in the state block not initialized - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoStateVariablesInitialized.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_invalid_co_co_priorities_correctly_specified(self): - """ - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoPrioritiesCorrectlySpecified.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) - - def test_valid_co_co_priorities_correctly_specified(self): - """ - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoPrioritiesCorrectlySpecified.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_co_co_resolution_legally_used(self): - """ - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoResolutionLegallyUsed.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 2) - - def test_valid_co_co_resolution_legally_used(self): - """ - """ - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoResolutionLegallyUsed.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_valid_co_co_vector_input_port(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), - 'CoCoVectorInputPortSizeAndType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - - def test_invalid_co_co_vector_input_port(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), - 'CoCoVectorInputPortSizeAndType.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 1) diff --git a/tests/function_parameter_templating_test.py b/tests/function_parameter_templating_test.py deleted file mode 100644 index e3cb89e41..000000000 --- a/tests/function_parameter_templating_test.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -# -# function_parameter_templating_test.py -# -# This file is part of NEST. -# -# Copyright (C) 2004 The NEST Initiative -# -# NEST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# NEST is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with NEST. If not, see . - -import os -import unittest - -from pynestml.symbol_table.symbol_table import SymbolTable -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.predefined_types import PredefinedTypes -from pynestml.symbols.predefined_units import PredefinedUnits -from pynestml.symbols.predefined_variables import PredefinedVariables -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.utils.logger import Logger, LoggingLevel -from pynestml.utils.model_parser import ModelParser - -# minor setup steps required -SymbolTable.initialize_symbol_table(ASTSourceLocation(start_line=0, start_column=0, end_line=0, end_column=0)) -PredefinedUnits.register_units() -PredefinedTypes.register_types() -PredefinedVariables.register_variables() -PredefinedFunctions.register_functions() - - -class FunctionParameterTemplatingTest(unittest.TestCase): - """ - This test is used to test the correct derivation of types when functions use templated type parameters. - """ - - def test(self): - Logger.init_logger(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), - "resources", "FunctionParameterTemplatingTest.nestml")))) - self.assertEqual(len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], - LoggingLevel.ERROR)), 7) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/nest_compartmental_tests/test__cocos.py b/tests/nest_compartmental_tests/test__cocos.py index dc4daa28c..7ee55f8a1 100644 --- a/tests/nest_compartmental_tests/test__cocos.py +++ b/tests/nest_compartmental_tests/test__cocos.py @@ -21,41 +21,39 @@ from __future__ import print_function +from typing import Optional + import os import pytest -from pynestml.frontend.frontend_configuration import FrontendConfiguration - -from pynestml.utils.ast_source_location import ASTSourceLocation +from pynestml.meta_model.ast_model import ASTModel from pynestml.symbol_table.symbol_table import SymbolTable from pynestml.symbols.predefined_functions import PredefinedFunctions from pynestml.symbols.predefined_types import PredefinedTypes from pynestml.symbols.predefined_units import PredefinedUnits from pynestml.symbols.predefined_variables import PredefinedVariables +from pynestml.utils.ast_source_location import ASTSourceLocation from pynestml.utils.logger import LoggingLevel, Logger from pynestml.utils.model_parser import ModelParser -@pytest.fixture -def setUp(): - Logger.init_logger(LoggingLevel.INFO) - SymbolTable.initialize_symbol_table( - ASTSourceLocation( - start_line=0, - start_column=0, - end_line=0, - end_column=0)) - PredefinedUnits.register_units() - PredefinedTypes.register_types() - PredefinedVariables.register_variables() - PredefinedFunctions.register_functions() - FrontendConfiguration.target_platform = "NEST_COMPARTMENTAL" - - class TestCoCos: - def test_invalid_cm_variables_declared(self, setUp): - model = ModelParser.parse_file( + @pytest.fixture(scope="module", autouse=True) + def setUp(self): + SymbolTable.initialize_symbol_table( + ASTSourceLocation( + start_line=0, + start_column=0, + end_line=0, + end_column=0)) + PredefinedUnits.register_units() + PredefinedTypes.register_types() + PredefinedVariables.register_variables() + PredefinedFunctions.register_functions() + + def test_invalid_cm_variables_declared(self): + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -63,11 +61,10 @@ def test_invalid_cm_variables_declared(self, setUp): 'invalid')), 'CoCoCmVariablesDeclared.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 5 + model, LoggingLevel.ERROR)) == 6 - def test_valid_cm_variables_declared(self, setUp): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( + def test_valid_cm_variables_declared(self): + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -75,12 +72,12 @@ def test_valid_cm_variables_declared(self, setUp): 'valid')), 'CoCoCmVariablesDeclared.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 0 + model, LoggingLevel.ERROR)) == 0 # it is currently not enforced for the non-cm parameter block, but cm # needs that - def test_invalid_cm_variable_has_rhs(self, setUp): - model = ModelParser.parse_file( + def test_invalid_cm_variable_has_rhs(self): + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -88,11 +85,11 @@ def test_invalid_cm_variable_has_rhs(self, setUp): 'invalid')), 'CoCoCmVariableHasRhs.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 2 + model, LoggingLevel.ERROR)) == 2 - def test_valid_cm_variable_has_rhs(self, setUp): + def test_valid_cm_variable_has_rhs(self): Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -100,12 +97,12 @@ def test_valid_cm_variable_has_rhs(self, setUp): 'valid')), 'CoCoCmVariableHasRhs.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 0 + model, LoggingLevel.ERROR)) == 0 # it is currently not enforced for the non-cm parameter block, but cm # needs that - def test_invalid_cm_v_comp_exists(self, setUp): - model = ModelParser.parse_file( + def test_invalid_cm_v_comp_exists(self): + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -113,11 +110,11 @@ def test_invalid_cm_v_comp_exists(self, setUp): 'invalid')), 'CoCoCmVcompExists.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 4 + model, LoggingLevel.ERROR)) == 4 - def test_valid_cm_v_comp_exists(self, setUp): + def test_valid_cm_v_comp_exists(self): Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( + model = self._parse_and_validate_model( os.path.join( os.path.realpath( os.path.join( @@ -125,4 +122,23 @@ def test_valid_cm_v_comp_exists(self, setUp): 'valid')), 'CoCoCmVcompExists.nestml')) assert len(Logger.get_all_messages_of_level_and_or_node( - model.get_model_list()[0], LoggingLevel.ERROR)) == 0 + model, LoggingLevel.ERROR)) == 0 + + def _parse_and_validate_model(self, fname: str) -> Optional[str]: + from pynestml.frontend.pynestml_frontend import generate_target + + Logger.init_logger(LoggingLevel.DEBUG) + + try: + generate_target(input_path=fname, target_platform="NONE", logging_level="DEBUG") + except BaseException: + return None + + ast_compilation_unit = ModelParser.parse_file(fname) + if ast_compilation_unit is None or len(ast_compilation_unit.get_model_list()) == 0: + return None + + model: ASTModel = ast_compilation_unit.get_model_list()[0] + model_name = model.get_name() + + return model_name diff --git a/tests/nest_tests/nest_code_generator_test.py b/tests/nest_tests/nest_code_generator_test.py deleted file mode 100644 index 64a183bd1..000000000 --- a/tests/nest_tests/nest_code_generator_test.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -# -# nest_code_generator_test.py -# -# This file is part of NEST. -# -# Copyright (C) 2004 The NEST Initiative -# -# NEST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# NEST is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with NEST. If not, see . - -import json -import os -import unittest - -from pynestml.codegeneration.nest_code_generator import NESTCodeGenerator -from pynestml.frontend.frontend_configuration import FrontendConfiguration -from pynestml.symbol_table.symbol_table import SymbolTable -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.predefined_types import PredefinedTypes -from pynestml.symbols.predefined_units import PredefinedUnits -from pynestml.symbols.predefined_variables import PredefinedVariables -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.utils.logger import Logger, LoggingLevel -from pynestml.utils.model_parser import ModelParser - - -class CodeGeneratorTest(unittest.TestCase): - """ - Tests code generator with an IAF psc and cond model, both with alpha and delta synaptic kernels - """ - - def setUp(self): - PredefinedUnits.register_units() - PredefinedTypes.register_types() - PredefinedFunctions.register_functions() - PredefinedVariables.register_variables() - SymbolTable.initialize_symbol_table(ASTSourceLocation(start_line=0, start_column=0, end_line=0, end_column=0)) - Logger.init_logger(LoggingLevel.INFO) - - self.target_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, 'target')))) - - def test_iaf_psc_alpha(self): - input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, 'models', 'neurons', 'iaf_psc_alpha_neuron.nestml')))) - - params = list() - params.append('--input_path') - params.append(input_path) - params.append('--logging_level') - params.append('INFO') - params.append('--target_path') - params.append(self.target_path) - params.append('--dev') - FrontendConfiguration.parse_config(params) - - compilation_unit = ModelParser.parse_file(input_path) - - nestCodeGenerator = NESTCodeGenerator() - nestCodeGenerator.generate_code(compilation_unit.get_model_list()) - - def test_iaf_psc_delta(self): - input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, 'models', 'neurons', 'iaf_psc_delta_neuron.nestml')))) - - params = list() - params.append('--input_path') - params.append(input_path) - params.append('--logging_level') - params.append('INFO') - params.append('--target_path') - params.append(self.target_path) - params.append('--dev') - FrontendConfiguration.parse_config(params) - - compilation_unit = ModelParser.parse_file(input_path) - - nestCodeGenerator = NESTCodeGenerator() - nestCodeGenerator.generate_code(compilation_unit.get_model_list()) - - def test_iaf_cond_alpha_functional(self): - input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, 'models', 'neurons', 'iaf_cond_alpha_neuron.nestml')))) - - params = list() - params.append('--input_path') - params.append(input_path) - params.append('--logging_level') - params.append('INFO') - params.append('--target_path') - params.append(self.target_path) - params.append('--dev') - FrontendConfiguration.parse_config(params) - - compilation_unit = ModelParser.parse_file(input_path) - iaf_cond_alpha_functional = list() - iaf_cond_alpha_functional.append(compilation_unit.get_model_list()[0]) - - nestCodeGenerator = NESTCodeGenerator() - nestCodeGenerator.generate_code(iaf_cond_alpha_functional) - - def test_iaf_psc_alpha_with_codegen_opts(self): - input_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, 'models', 'neurons', 'iaf_psc_alpha_neuron.nestml')))) - - code_opts_path = str(os.path.realpath(os.path.join(os.path.dirname(__file__), - os.path.join('resources', 'code_options.json')))) - codegen_opts = {"templates": { - "path": "resources_nest/point_neuron", - "model_templates": { - "neuron": ['@NEURON_NAME@.cpp.jinja2', '@NEURON_NAME@.h.jinja2'], - "synapse": [] - }, - "module_templates": ['setup/CMakeLists.txt.jinja2', - 'setup/@MODULE_NAME@.h.jinja2', 'setup/@MODULE_NAME@.cpp.jinja2'] - }} - - with open(code_opts_path, 'w+') as f: - json.dump(codegen_opts, f) - - params = list() - params.append('--input_path') - params.append(input_path) - params.append('--logging_level') - params.append('INFO') - params.append('--target_path') - params.append(self.target_path) - params.append('--dev') - params.append('--codegen_opts') - params.append(code_opts_path) - FrontendConfiguration.parse_config(params) - - compilation_unit = ModelParser.parse_file(input_path) - - nestCodeGenerator = NESTCodeGenerator(codegen_opts) - nestCodeGenerator.generate_code(compilation_unit.get_model_list()) - - def tearDown(self): - import shutil - shutil.rmtree(self.target_path) diff --git a/tests/nest_tests/nest_delay_based_variables_test.py b/tests/nest_tests/nest_delay_based_variables_test.py index 51f863e19..a11c280f2 100644 --- a/tests/nest_tests/nest_delay_based_variables_test.py +++ b/tests/nest_tests/nest_delay_based_variables_test.py @@ -19,13 +19,12 @@ # You should have received a copy of the GNU General Public License # along with NEST. If not, see . +from typing import List + import numpy as np import os -from typing import List import pytest -import nest - try: import matplotlib import matplotlib.pyplot as plt @@ -34,15 +33,12 @@ except BaseException: TEST_PLOTS = False +import nest + from pynestml.codegeneration.nest_tools import NESTTools from pynestml.frontend.pynestml_frontend import generate_nest_target -target_path = "target_delay" -logging_level = "DEBUG" -suffix = "_nestml" - - def plot_fig(times, recordable_events_delay: dict, recordable_events: dict, filename: str): fig, axes = plt.subplots(len(recordable_events), 1, figsize=(7, 9), sharex=True) for i, recordable_name in enumerate(recordable_events_delay.keys()): @@ -86,6 +82,9 @@ def run_simulation(neuron_model_name: str, module_name: str, recordables: List[s ("DelayDifferentialEquationsWithNumericSolver.nestml", "dde_numeric_nestml", ["x", "z"]), ("DelayDifferentialEquationsWithMixedSolver.nestml", "dde_mixed_nestml", ["x", "z"])]) def test_dde_with_analytic_solver(file_name: str, neuron_model_name: str, recordables: List[str]): + target_path = "target_delay" + logging_level = "DEBUG" + suffix = "_nestml" input_path = os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), "resources", file_name))) module_name = neuron_model_name + "_module" print("Module name: ", module_name) @@ -112,16 +111,3 @@ def test_dde_with_analytic_solver(file_name: str, neuron_model_name: str, record if neuron_model_name == "dde_analytic_nestml": np.testing.assert_allclose(recordable_events_delay[recordables[1]][int(delay):], recordable_events[recordables[1]][:-int(delay)]) - - @pytest.fixture(scope="function", autouse=True) - def cleanup(self): - # Run the test - yield - - # clean up - import shutil - if self.target_path: - try: - shutil.rmtree(self.target_path) - except Exception: - pass diff --git a/tests/nest_tests/nest_integration_test.py b/tests/nest_tests/nest_integration_test.py index abfa47ea1..77ea6a06e 100644 --- a/tests/nest_tests/nest_integration_test.py +++ b/tests/nest_tests/nest_integration_test.py @@ -62,7 +62,7 @@ def generate_all_models(self): "models/neurons/iaf_psc_alpha_neuron.nestml", "models/neurons/iaf_psc_exp_neuron.nestml", "models/neurons/iaf_psc_delta_neuron.nestml"], - target_path="/tmp/nestml-allmodels", + target_path="/tmp/nestml-allmodels-convolutions-transformer", logging_level="DEBUG", module_name="nestml_allmodels_module", suffix="_nestml", @@ -134,9 +134,8 @@ def test_nest_integration(self): self._test_model_equivalence_spiking("hh_psc_alpha", "hh_psc_alpha_neuron_nestml", tolerance=1E-5, nestml_model_parameters=nestml_hh_psc_alpha_model_parameters) self._test_model_equivalence_fI_curve("hh_psc_alpha", "hh_psc_alpha_neuron_nestml", nestml_model_parameters=nestml_hh_psc_alpha_model_parameters) - nestml_hh_cond_exp_traub_model_parameters = {"gsl_abs_error_tol": 1E-3, "gsl_rel_error_tol": 0.} # matching the defaults in NEST - self._test_model_equivalence_subthreshold("hh_cond_exp_traub", "hh_cond_exp_traub_neuron_nestml", nestml_model_parameters=nestml_hh_cond_exp_traub_model_parameters) - self._test_model_equivalence_fI_curve("hh_cond_exp_traub", "hh_cond_exp_traub_neuron_nestml", nestml_model_parameters=nestml_hh_cond_exp_traub_model_parameters) + # self._test_model_equivalence_subthreshold("hh_cond_exp_traub", "hh_cond_exp_traub_neuron_nestml") # unfortunately, cannot test this due to small differences in generated code (multiplying by 1000 is not the same, numerically, as dividing by (1 / 1000)). See https://github.com/nest/nestml/issues/984 + self._test_model_equivalence_fI_curve("hh_cond_exp_traub", "hh_cond_exp_traub_neuron_nestml") self._test_model_equivalence_subthreshold("aeif_cond_exp", "aeif_cond_exp_neuron_alt_nestml", kernel_opts={"resolution": .01}) # needs resolution 0.01 because the NEST model overrides this internally. Subthreshold only because threshold detection is inside the while...gsl_odeiv_evolve_apply() loop in NEST but outside the loop (strictly after gsl_odeiv_evolve_apply()) in NESTML, causing spike times to differ slightly self._test_model_equivalence_fI_curve("aeif_cond_exp", "aeif_cond_exp_neuron_alt_nestml") diff --git a/tests/nest_tests/non_linear_dendrite_test.py b/tests/nest_tests/non_linear_dendrite_test.py index 2da978976..d9d92dce3 100644 --- a/tests/nest_tests/non_linear_dendrite_test.py +++ b/tests/nest_tests/non_linear_dendrite_test.py @@ -47,11 +47,6 @@ class NestNonLinearDendriteTest(unittest.TestCase): @pytest.mark.skipif(NESTTools.detect_nest_version().startswith("v2"), reason="This test does not support NEST 2") def test_non_linear_dendrite(self): - MAX_SSE = 1E-12 - - I_dend_alias_name = "I_dend" # synaptic current - I_dend_internal_name = "I_kernel2__X__I_2" # alias for the synaptic current - input_path = os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), "resources")), "iaf_psc_exp_nonlineardendrite.nestml") target_path = "target" logging_level = "DEBUG" @@ -74,19 +69,17 @@ def test_non_linear_dendrite(self): nest.Connect(sg, nrn, syn_spec={"receptor_type": 2, "weight": 30., "delay": 1.}) mm = nest.Create("multimeter") - mm.set({"record_from": [I_dend_alias_name, I_dend_internal_name, "V_m", "dend_curr_enabled", "I_dend_ap"]}) + mm.set({"record_from": ["I_dend", "V_m", "dend_curr_enabled", "I_dend_ap"]}) nest.Connect(mm, nrn) nest.Simulate(100.0) timevec = mm.get("events")["times"] - I_dend_alias_ts = mm.get("events")[I_dend_alias_name] - I_dend_internal_ts = mm.get("events")[I_dend_internal_name] + I_dend = mm.get("events")["I_dend"] if TEST_PLOTS: fig, ax = plt.subplots(3, 1) - ax[0].plot(timevec, I_dend_alias_ts, label="aliased I_dend_syn") - ax[0].plot(timevec, I_dend_internal_ts, label="internal I_dend_syn") + ax[0].plot(timevec, I_dend, label="I_dend_syn") ax[0].legend() ax_ = ax[0].twinx() ax_.plot(timevec, mm.get("events")["dend_curr_enabled"], color="green") @@ -101,10 +94,8 @@ def test_non_linear_dendrite(self): plt.suptitle("Reset of synaptic integration after dendritic spike") plt.savefig("/tmp/nestml_non_linear_dend_test.png") - assert np.all(I_dend_alias_ts == I_dend_internal_ts), "Variable " + str(I_dend_alias_name) + " and (internal) variable " + str(I_dend_internal_name) + " should measure the same thing, but discrepancy in values occurred." - tidx = np.argmin((timevec - 40)**2) assert mm.get("events")["I_dend_ap"][tidx] > 0., "Expected a dendritic action potential around t = 40 ms, but dendritic action potential current is zero" assert mm.get("events")["dend_curr_enabled"][tidx] == 0., "Dendritic synaptic current should be disabled during dendritic action potential" tidx_ap_end = tidx + np.where(mm.get("events")["dend_curr_enabled"][tidx:] == 1.)[0][0] - assert np.all(I_dend_alias_ts[tidx_ap_end:] == 0.), "After dendritic spike, dendritic current should be reset to 0 and stay at 0." + assert np.all(I_dend[tidx_ap_end:] == 0.), "After dendritic spike, dendritic current should be reset to 0 and stay at 0." diff --git a/tests/nest_tests/recordable_variables_test.py b/tests/nest_tests/recordable_variables_test.py index b8c126d23..04e2bce00 100644 --- a/tests/nest_tests/recordable_variables_test.py +++ b/tests/nest_tests/recordable_variables_test.py @@ -62,7 +62,7 @@ def test_recordable_variables(self): sg = nest.Create("spike_generator", params={"spike_times": [20., 80.]}) nest.Connect(sg, neuron) - mm = nest.Create('multimeter', params={'record_from': ['V_ex', 'V_rel', 'V_m', 'I_kernel__X__spikes'], + mm = nest.Create('multimeter', params={'record_from': ['V_ex', 'V_rel', 'V_m', 'I_kernel__conv__spikes'], 'interval': 0.1}) nest.Connect(mm, neuron) diff --git a/tests/nest_tests/resources/BiexponentialPostSynapticResponse.nestml b/tests/nest_tests/resources/BiexponentialPostSynapticResponse.nestml index 2fd3f13ca..a089fb7b3 100644 --- a/tests/nest_tests/resources/BiexponentialPostSynapticResponse.nestml +++ b/tests/nest_tests/resources/BiexponentialPostSynapticResponse.nestml @@ -49,10 +49,10 @@ model biexp_postsynaptic_response_neuron: equations: kernel g_ex' = -g_ex / tau_syn_decay_E + h_ex, - h_ex' = -h_ex / tau_syn_rise_E + h_ex' = -h_ex / tau_syn_rise_E kernel g_in' = -g_in / tau_syn_decay_I + g_in$, - g_in$' = -g_in$ / tau_syn_rise_I + g_in$' = -g_in$ / tau_syn_rise_I kernel g_gap = g_gap_const * (exp(-t/tau_syn_decay_gap) - exp(-t/tau_syn_rise_gap)) kernel g_GABA'' = -(g_GABA + g_GABA' * (tau_syn_decay_E + tau_syn_rise_E)) / (tau_syn_decay_E * tau_syn_rise_E) diff --git a/tests/nest_tests/resources/iaf_psc_exp_nonlineardendrite.nestml b/tests/nest_tests/resources/iaf_psc_exp_nonlineardendrite.nestml index a81642585..04a992bef 100644 --- a/tests/nest_tests/resources/iaf_psc_exp_nonlineardendrite.nestml +++ b/tests/nest_tests/resources/iaf_psc_exp_nonlineardendrite.nestml @@ -37,13 +37,16 @@ model iaf_psc_exp_nonlineardendrite: t_dend_ap ms = 0 ms # dendritic action potential timer dend_curr_enabled real = 1. # set to 1 to allow synaptic dendritic currents to contribute to V_m integration, 0 otherwise I_dend_ap pA = 0 pA + I_dend pA = 0 pA + I_dend$ pA/s= 0 pA/s equations: kernel I_kernel1 = exp(-t / tau_syn1) - kernel I_kernel2 = (e / tau_syn2) * t * exp(-t / tau_syn2) kernel I_kernel3 = exp(-t / tau_syn3) - recordable inline I_dend pA = convolve(I_kernel2, I_2) * pA + # alpha kernel for I_dend + I_dend' = I_dend$ - I_dend / tau_syn2 + I_dend$' = -I_dend$ / tau_syn2 inline I_syn pA = convolve(I_kernel1, I_1) * pA + dend_curr_enabled * I_dend + I_dend_ap + convolve(I_kernel3, I_3) * pA + I_e @@ -77,6 +80,9 @@ model iaf_psc_exp_nonlineardendrite: # solve ODEs integrate_odes() + onReceive(I_2): + I_dend$ += I_2 * 1 pA * 1E3 # s/ms is to convert (1/s) units of spiking input port into (1/ms) because time constants are in ms + onCondition(t_dend_ap > 0 ms): # we are in the middle of emitting a dendritic action potential t_dend_ap -= resolution() @@ -84,7 +90,7 @@ model iaf_psc_exp_nonlineardendrite: t_dend_ap = 0 ms dend_curr_enabled = 1. I_dend = 0 pA - I_dend' = 0 * s**-1 + I_dend$ = 0 pA/s I_dend_ap = 0 pA onCondition(I_dend > i_th): diff --git a/tests/nest_tests/resources/integrate_odes_test_params.nestml b/tests/nest_tests/resources/integrate_odes_test_params.nestml index d07fe8fd4..d6430e537 100644 --- a/tests/nest_tests/resources/integrate_odes_test_params.nestml +++ b/tests/nest_tests/resources/integrate_odes_test_params.nestml @@ -8,7 +8,6 @@ model integrate_odes_test: update: integrate_odes(2 * test_1) - integrate_odes(test_3) integrate_odes(100 ms) integrate_odes(test_1) integrate_odes(test_2) diff --git a/tests/nest_tests/resources/integrate_odes_test_params2.nestml b/tests/nest_tests/resources/integrate_odes_test_params2.nestml new file mode 100644 index 000000000..616401e48 --- /dev/null +++ b/tests/nest_tests/resources/integrate_odes_test_params2.nestml @@ -0,0 +1,10 @@ +""" +Model for testing the integrate_odes() function. +""" +model integrate_odes_test: + state: + test_1 real = 0. + test_2 real = 0. + + update: + integrate_odes(test_3) diff --git a/tests/nest_tests/resources/test_delta_kernel_neuron.nestml b/tests/nest_tests/resources/test_delta_kernel_neuron.nestml index 20f4bebc5..b9f53ba5c 100644 --- a/tests/nest_tests/resources/test_delta_kernel_neuron.nestml +++ b/tests/nest_tests/resources/test_delta_kernel_neuron.nestml @@ -10,12 +10,13 @@ Used in NESTML unit testing. model test_delta_kernel_neuron: state: x mV = 0 mV + y mV = 0 mV equations: kernel delta_kernel = delta(t) recordable inline psp mV = convolve(delta_kernel, spikes) * mV - # x' = -x / tau_m + psp / ms - x' = -x / tau_m + (spikes * V) + y' = -y / tau_m + psp / ms + x' = -x / tau_m + spikes * mV parameters: tau_m ms = 10 ms diff --git a/tests/nest_tests/test_biexponential_synapse_kernel.py b/tests/nest_tests/test_biexponential_synapse_kernel.py index 0ed0954d8..4f8d260c3 100644 --- a/tests/nest_tests/test_biexponential_synapse_kernel.py +++ b/tests/nest_tests/test_biexponential_synapse_kernel.py @@ -75,7 +75,7 @@ def test_biexp_synapse(self): nest.Connect(sg4, neuron, syn_spec={"receptor_type": 4, "weight": 100.}) i_1 = nest.Create("multimeter", params={"record_from": [ - "g_gap__X__spikeGap", "g_ex__X__spikeExc", "g_in__X__spikeInh", "g_GABA__X__spikeGABA"], "interval": .1}) + "g_gap__conv__spikeGap", "g_ex__conv__spikeExc", "g_in__conv__spikeInh", "g_GABA__conv__spikeGABA"], "interval": .1}) nest.Connect(i_1, neuron) vm_1 = nest.Create("voltmeter") @@ -105,16 +105,16 @@ def plot(self, vm_1, i_1, sd): ax[0].scatter(sd.events["times"], np.mean(vm_1["V_m"]) * np.ones_like(sd.events["times"])) - ax[1].plot(i_1["times"], i_1["g_gap__X__spikeGap"], label="g_gap__X__spikeGap") + ax[1].plot(i_1["times"], i_1["g_gap__conv__spikeGap"], label="g_gap__conv__spikeGap") ax[1].set_ylabel("current") - ax[2].plot(i_1["times"], i_1["g_ex__X__spikeExc"], label="g_ex__X__spikeExc") + ax[2].plot(i_1["times"], i_1["g_ex__conv__spikeExc"], label="g_ex__conv__spikeExc") ax[2].set_ylabel("current") - ax[3].plot(i_1["times"], i_1["g_in__X__spikeInh"], label="g_in__X__spikeInh") + ax[3].plot(i_1["times"], i_1["g_in__conv__spikeInh"], label="g_in__conv__spikeInh") ax[3].set_ylabel("current") - ax[4].plot(i_1["times"], i_1["g_GABA__X__spikeGABA"], label="g_GABA__X__spikeGABA") + ax[4].plot(i_1["times"], i_1["g_GABA__conv__spikeGABA"], label="g_GABA__conv__spikeGABA") ax[4].set_ylabel("current") for _ax in ax: diff --git a/tests/nest_tests/test_delta_kernel.py b/tests/nest_tests/test_delta_kernel.py new file mode 100644 index 000000000..82fae6205 --- /dev/null +++ b/tests/nest_tests/test_delta_kernel.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# +# test_delta_kernel.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +import nest +import numpy as np +import os +import pytest + +from pynestml.codegeneration.nest_tools import NESTTools +from pynestml.frontend.pynestml_frontend import generate_nest_target + +try: + import matplotlib + import matplotlib.pyplot as plt + TEST_PLOTS = True +except BaseException: + TEST_PLOTS = False + + +@pytest.mark.skipif(NESTTools.detect_nest_version().startswith("v2"), + reason="This test does not support NEST 2") +class TestDeltaKernel: + def test_delta_kernel(self): + input_path = os.path.join(os.path.realpath(os.path.join( + os.path.dirname(__file__), "resources", "test_delta_kernel_neuron.nestml"))) + target_path = "target" + logging_level = "DEBUG" + module_name = "nestmlmodule" + suffix = "_nestml" + + generate_nest_target(input_path, + target_path=target_path, + logging_level=logging_level, + module_name=module_name, + suffix=suffix) + + nest.ResetKernel() + nest.Install(module_name) + nest.set_verbosity("M_ALL") + nest.resolution = 1 + + nest_neuron = nest.Create("iaf_psc_delta") + nest_neuron.V_th = 1E99 + nest_neuron.V_m = 0. + nest_neuron.E_L = 0. + nest_neuron.V_reset = 0. + + # network construction + neuron = nest.Create("test_delta_kernel_neuron_nestml") + + sg = nest.Create("spike_generator", params={"spike_times": [5., 25.]}) + nest.Connect(sg, neuron, syn_spec={"weight": 100., "delay": 1.}) + nest.Connect(sg, nest_neuron, syn_spec={"weight": 100., "delay": 1.}) + + mm = nest.Create("multimeter", params={"record_from": ["x", "y"], + "interval": nest.resolution}) + nest.Connect(mm, neuron) + + nest_mm = nest.Create("multimeter", params={"record_from": ["V_m"], + "interval": nest.resolution}) + nest.Connect(nest_mm, nest_neuron) + + # simulate + nest.Simulate(25.) + + conn = nest.GetConnections(source=sg, + target=neuron) + + conn.weight = -conn.weight + + conn = nest.GetConnections(source=sg, + target=nest_neuron) + + conn.weight = -conn.weight + + nest.Simulate(25.) + + # analysis + if TEST_PLOTS: + fig, ax = plt.subplots(dpi=300., nrows=2) + + ax[0].plot(mm.get()["events"]["times"], mm.get()["events"]["x"], label="x") + ax[0].plot(mm.get()["events"]["times"], mm.get()["events"]["y"], label="y") + ax[0].plot(nest_mm.get()["events"]["times"], nest_mm.get()["events"]["V_m"], label="NEST") + + ax[1].semilogy(nest_mm.get()["events"]["times"], np.abs(nest_mm.get()["events"]["V_m"] - mm.get()["events"]["x"]), label="x") + ax[1].semilogy(nest_mm.get()["events"]["times"], np.abs(nest_mm.get()["events"]["V_m"] - mm.get()["events"]["y"]), label="y") + for _ax in ax: + _ax.legend() + _ax.grid() + + fig.savefig("/tmp/test_delta_kernel.png") + + # testing + np.testing.assert_allclose(nest_mm.get()["events"]["V_m"], mm.get()["events"]["x"]) + np.testing.assert_allclose(nest_mm.get()["events"]["V_m"], mm.get()["events"]["y"]) diff --git a/tests/nest_tests/test_integrate_odes.py b/tests/nest_tests/test_integrate_odes.py index 99b94c6ca..6ddb699b4 100644 --- a/tests/nest_tests/test_integrate_odes.py +++ b/tests/nest_tests/test_integrate_odes.py @@ -27,16 +27,9 @@ import nest -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.symbol_table.symbol_table import SymbolTable -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.predefined_types import PredefinedTypes -from pynestml.symbols.predefined_units import PredefinedUnits -from pynestml.symbols.predefined_variables import PredefinedVariables from pynestml.codegeneration.nest_tools import NESTTools -from pynestml.frontend.pynestml_frontend import generate_nest_target +from pynestml.frontend.pynestml_frontend import generate_nest_target, generate_target from pynestml.utils.logger import LoggingLevel, Logger -from pynestml.utils.model_parser import ModelParser try: import matplotlib @@ -227,12 +220,15 @@ def test_integrate_odes_nonlinear(self): def test_integrate_odes_params(self): r"""Test the integrate_odes() function, in particular with respect to the parameter types.""" - Logger.init_logger(LoggingLevel.INFO) - SymbolTable.initialize_symbol_table(ASTSourceLocation(start_line=0, start_column=0, end_line=0, end_column=0)) - PredefinedUnits.register_units() - PredefinedTypes.register_types() - PredefinedVariables.register_variables() - PredefinedFunctions.register_functions() - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join("resources", "integrate_odes_test_params.nestml")))) - assert len(Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)) == 6 + fname = os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join("resources", "integrate_odes_test_params.nestml"))) + generate_target(input_path=fname, target_platform="NONE", logging_level="DEBUG") + + assert len(Logger.get_all_messages_of_level_and_or_node("integrate_odes_test", LoggingLevel.ERROR)) == 2 + + def test_integrate_odes_params2(self): + r"""Test the integrate_odes() function, in particular with respect to non-existent parameter variables.""" + + fname = os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join("resources", "integrate_odes_test_params2.nestml"))) + generate_target(input_path=fname, target_platform="NONE", logging_level="DEBUG") + + assert len(Logger.get_all_messages_of_level_and_or_node("integrate_odes_test", LoggingLevel.ERROR)) == 2 diff --git a/tests/nest_tests/test_priority_synapse.py b/tests/nest_tests/test_priority_synapse.py index a6d9743b8..fbaea92d5 100644 --- a/tests/nest_tests/test_priority_synapse.py +++ b/tests/nest_tests/test_priority_synapse.py @@ -129,7 +129,7 @@ def run_nest_simulation(self, neuron_model_name, spikedet_pre = nest.Create("spike_recorder") spikedet_post = nest.Create("spike_recorder") - # mm = nest.Create("multimeter", params={"record_from" : ["V_m", "post_trace_kernel__for_stdp_nestml__X__post_spikes__for_stdp_nestml"]}) + # mm = nest.Create("multimeter", params={"record_from" : ["V_m", "post_trace_kernel__for_stdp_nestml__conv__post_spikes__for_stdp_nestml"]}) nest.Connect(pre_sg, pre_neuron, "one_to_one", syn_spec={"delay": 1.}) nest.Connect(post_sg, post_neuron, "one_to_one", syn_spec={"delay": 1., "weight": 9999.}) diff --git a/tests/spinnaker_tests/test_spinnaker_iaf_psc_exp.py b/tests/spinnaker_tests/test_spinnaker_iaf_psc_exp.py index bd9f0a8c9..d4b556fb3 100644 --- a/tests/spinnaker_tests/test_spinnaker_iaf_psc_exp.py +++ b/tests/spinnaker_tests/test_spinnaker_iaf_psc_exp.py @@ -66,7 +66,7 @@ def test_iaf_psc_exp(self): # TODO: Set names for exitatory input, membrane potential and synaptic response exc_input = "exc_spikes" membranePot = "V_m" - synapticRsp = "I_kernel_exc__X__exc_spikes" + synapticRsp = "I_kernel_exc__conv__exc_spikes" # Set the run time of the execution run_time = 150 diff --git a/tests/test_cocos.py b/tests/test_cocos.py new file mode 100644 index 000000000..74e83ea78 --- /dev/null +++ b/tests/test_cocos.py @@ -0,0 +1,403 @@ +# -*- coding: utf-8 -*- +# +# test_cocos.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +from __future__ import print_function + +from typing import Optional + +import os +import pytest + +from pynestml.meta_model.ast_model import ASTModel +from pynestml.symbol_table.symbol_table import SymbolTable +from pynestml.symbols.predefined_functions import PredefinedFunctions +from pynestml.symbols.predefined_types import PredefinedTypes +from pynestml.symbols.predefined_units import PredefinedUnits +from pynestml.symbols.predefined_variables import PredefinedVariables +from pynestml.utils.ast_source_location import ASTSourceLocation +from pynestml.utils.logger import LoggingLevel, Logger +from pynestml.utils.model_parser import ModelParser + + +class TestCoCos: + + @pytest.fixture(scope="module", autouse=True) + def setUp(self): + SymbolTable.initialize_symbol_table( + ASTSourceLocation( + start_line=0, + start_column=0, + end_line=0, + end_column=0)) + PredefinedUnits.register_units() + PredefinedTypes.register_types() + PredefinedVariables.register_variables() + PredefinedFunctions.register_functions() + + def test_invalid_element_defined_after_usage(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVariableDefinedAfterUsage.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_element_defined_after_usage(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVariableDefinedAfterUsage.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_element_in_same_line(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoElementInSameLine.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_element_in_same_line(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoElementInSameLine.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_integrate_odes_called_if_equations_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoIntegrateOdesCalledIfEquationsDefined.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_integrate_odes_called_if_equations_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoIntegrateOdesCalledIfEquationsDefined.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_element_not_defined_in_scope(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVariableNotDefined.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 6 + + def test_valid_element_not_defined_in_scope(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVariableNotDefined.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_variable_with_same_name_as_unit(self): + Logger.set_logging_level(LoggingLevel.NO) + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVariableWithSameNameAsUnit.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.WARNING)) == 3 + + def test_invalid_variable_redeclaration(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVariableRedeclared.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_variable_redeclaration(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVariableRedeclared.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_each_block_unique(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoEachBlockUnique.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_each_block_unique(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoEachBlockUnique.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_function_unique_and_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoFunctionNotUnique.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 8 + + def test_valid_function_unique_and_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoFunctionNotUnique.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_inline_expressions_have_rhs(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInlineExpressionHasNoRhs.nestml')) + assert model is None + + def test_valid_inline_expressions_have_rhs(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInlineExpressionHasNoRhs.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_inline_expression_has_several_lhs(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInlineExpressionWithSeveralLhs.nestml')) + assert model is None + + def test_valid_inline_expression_has_several_lhs(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInlineExpressionWithSeveralLhs.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_no_values_assigned_to_input_ports(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoValueAssignedToInputPort.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_no_values_assigned_to_input_ports(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoValueAssignedToInputPort.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_order_of_equations_correct(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoNoOrderOfEquations.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_order_of_equations_correct(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoNoOrderOfEquations.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_numerator_of_unit_one(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoUnitNumeratorNotOne.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_numerator_of_unit_one(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoUnitNumeratorNotOne.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_names_of_neurons_unique(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoMultipleNeuronsWithEqualName.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 3 + + def test_valid_names_of_neurons_unique(self): + self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoMultipleNeuronsWithEqualName.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(None, LoggingLevel.ERROR)) == 0 + + def test_invalid_no_nest_collision(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoNestNamespaceCollision.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_no_nest_collision(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoNestNamespaceCollision.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_redundant_input_port_keywords_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInputPortWithRedundantTypes.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_redundant_input_port_keywords_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInputPortWithRedundantTypes.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_parameters_assigned_only_in_parameters_block(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoParameterAssignedOutsideBlock.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_parameters_assigned_only_in_parameters_block(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoParameterAssignedOutsideBlock.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_inline_expressions_assigned_only_in_declaration(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoAssignmentToInlineExpression.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_invalid_internals_assigned_only_in_internals_block(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInternalAssignedOutsideBlock.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_internals_assigned_only_in_internals_block(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInternalAssignedOutsideBlock.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_function_with_wrong_arg_number_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoFunctionCallNotConsistentWrongArgNumber.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_function_with_wrong_arg_number_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoFunctionCallNotConsistentWrongArgNumber.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_init_values_have_rhs_and_ode(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInitValuesWithoutOde.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.WARNING)) == 2 + + def test_valid_init_values_have_rhs_and_ode(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInitValuesWithoutOde.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.WARNING)) == 2 + + def test_invalid_incorrect_return_stmt_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoIncorrectReturnStatement.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 8 + + def test_valid_incorrect_return_stmt_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoIncorrectReturnStatement.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_ode_vars_outside_init_block_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoOdeVarNotInInitialValues.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_ode_vars_outside_init_block_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoOdeVarNotInInitialValues.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_convolve_correctly_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoConvolveNotCorrectlyProvided.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_convolve_correctly_defined(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoConvolveNotCorrectlyProvided.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_vector_in_non_vector_declaration_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVectorInNonVectorDeclaration.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_vector_in_non_vector_declaration_detected(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVectorInNonVectorDeclaration.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_vector_parameter_declaration(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVectorParameterDeclaration.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_vector_parameter_declaration(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVectorParameterDeclaration.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_vector_parameter_type(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVectorParameterType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_vector_parameter_type(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVectorParameterType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_vector_parameter_size(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVectorDeclarationSize.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_vector_parameter_size(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVectorDeclarationSize.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_convolve_correctly_parameterized(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoConvolveNotCorrectlyParametrized.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_convolve_correctly_parameterized(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoConvolveNotCorrectlyParametrized.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_invariant_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoInvariantNotBool.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_invariant_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoInvariantNotBool.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_expression_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoIllegalExpression.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_expression_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoIllegalExpression.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_compound_expression_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CompoundOperatorWithDifferentButCompatibleUnits.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 10 + + def test_valid_compound_expression_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CompoundOperatorWithDifferentButCompatibleUnits.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_ode_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoOdeIncorrectlyTyped.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) > 0 + + def test_valid_ode_correctly_typed(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoOdeCorrectlyTyped.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_output_block_defined_if_emit_call(self): + """test that an error is raised when the emit_spike() function is called by the neuron, but an output block is not defined""" + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoOutputPortDefinedIfEmitCall.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) > 0 + + def test_invalid_output_port_defined_if_emit_call(self): + """test that an error is raised when the emit_spike() function is called by the neuron, but a spiking output port is not defined""" + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoOutputPortDefinedIfEmitCall-2.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) > 0 + + def test_valid_output_port_defined_if_emit_call(self): + """test that no error is raised when the output block is missing, but not emit_spike() functions are called""" + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoOutputPortDefinedIfEmitCall.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_valid_coco_kernel_type(self): + """ + Test the functionality of CoCoKernelType. + """ + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoKernelType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_coco_kernel_type(self): + """ + Test the functionality of CoCoKernelType. + """ + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoKernelType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_invalid_coco_kernel_type_initial_values(self): + """ + Test the functionality of CoCoKernelType. + """ + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoKernelTypeInitialValues.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 4 + + def test_valid_coco_state_variables_initialized(self): + """ + Test that the CoCo condition is applicable for all the variables in the state block initialized with a value + """ + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoStateVariablesInitialized.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_coco_state_variables_initialized(self): + """ + Test that the CoCo condition is applicable for all the variables in the state block not initialized + """ + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoStateVariablesInitialized.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_invalid_co_co_priorities_correctly_specified(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoPrioritiesCorrectlySpecified.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def test_valid_co_co_priorities_correctly_specified(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoPrioritiesCorrectlySpecified.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_co_co_resolution_legally_used(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoResolutionLegallyUsed.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 2 + + def test_valid_co_co_resolution_legally_used(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoResolutionLegallyUsed.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_valid_co_co_vector_input_port(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'valid')), 'CoCoVectorInputPortSizeAndType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 0 + + def test_invalid_co_co_vector_input_port(self): + model = self._parse_and_validate_model(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'invalid')), 'CoCoVectorInputPortSizeAndType.nestml')) + assert len(Logger.get_all_messages_of_level_and_or_node(model, LoggingLevel.ERROR)) == 1 + + def _parse_and_validate_model(self, fname: str) -> Optional[str]: + from pynestml.frontend.pynestml_frontend import generate_target + + Logger.init_logger(LoggingLevel.DEBUG) + + try: + generate_target(input_path=fname, target_platform="NONE", logging_level="DEBUG") + except BaseException: + return None + + ast_compilation_unit = ModelParser.parse_file(fname) + if ast_compilation_unit is None or len(ast_compilation_unit.get_model_list()) == 0: + return None + + model: ASTModel = ast_compilation_unit.get_model_list()[0] + model_name = model.get_name() + + return model_name diff --git a/tests/test_function_parameter_templating.py b/tests/test_function_parameter_templating.py new file mode 100644 index 000000000..b93e06780 --- /dev/null +++ b/tests/test_function_parameter_templating.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# +# test_function_parameter_templating.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +import os + +from pynestml.utils.logger import Logger, LoggingLevel +from pynestml.frontend.pynestml_frontend import generate_target + + +class TestFunctionParameterTemplating: + """ + This test is used to test the correct derivation of types when functions use templated type parameters. + """ + + def test(self): + fname = os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), "resources", "FunctionParameterTemplatingTest.nestml"))) + generate_target(input_path=fname, target_platform="NONE", logging_level="DEBUG") + assert len(Logger.get_all_messages_of_level_and_or_node("templated_function_parameters_type_test", LoggingLevel.ERROR)) == 5 diff --git a/tests/test_unit_system.py b/tests/test_unit_system.py new file mode 100644 index 000000000..5b71ce6e1 --- /dev/null +++ b/tests/test_unit_system.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# +# test_unit_system.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +import os +import pytest + +from pynestml.codegeneration.printers.constant_printer import ConstantPrinter +from pynestml.codegeneration.printers.cpp_expression_printer import CppExpressionPrinter +from pynestml.codegeneration.printers.cpp_simple_expression_printer import CppSimpleExpressionPrinter +from pynestml.codegeneration.printers.cpp_type_symbol_printer import CppTypeSymbolPrinter +from pynestml.codegeneration.printers.cpp_variable_printer import CppVariablePrinter +from pynestml.codegeneration.printers.nest_cpp_function_call_printer import NESTCppFunctionCallPrinter +from pynestml.codegeneration.printers.nestml_variable_printer import NESTMLVariablePrinter +from pynestml.frontend.pynestml_frontend import generate_target +from pynestml.symbol_table.symbol_table import SymbolTable +from pynestml.symbols.predefined_functions import PredefinedFunctions +from pynestml.symbols.predefined_types import PredefinedTypes +from pynestml.symbols.predefined_units import PredefinedUnits +from pynestml.symbols.predefined_variables import PredefinedVariables +from pynestml.utils.ast_source_location import ASTSourceLocation +from pynestml.utils.logger import Logger, LoggingLevel +from pynestml.utils.model_parser import ModelParser + + +class TestUnitSystem: + r""" + Test class for units system. + """ + + @pytest.fixture(scope="class", autouse=True) + def setUp(self, request): + Logger.set_logging_level(LoggingLevel.INFO) + + SymbolTable.initialize_symbol_table(ASTSourceLocation(start_line=0, start_column=0, end_line=0, end_column=0)) + + PredefinedUnits.register_units() + PredefinedTypes.register_types() + PredefinedVariables.register_variables() + PredefinedFunctions.register_functions() + + Logger.init_logger(LoggingLevel.INFO) + + variable_printer = NESTMLVariablePrinter(None) + function_call_printer = NESTCppFunctionCallPrinter(None) + cpp_variable_printer = CppVariablePrinter(None) + self.printer = CppExpressionPrinter(CppSimpleExpressionPrinter(cpp_variable_printer, + ConstantPrinter(), + function_call_printer)) + cpp_variable_printer._expression_printer = self.printer + variable_printer._expression_printer = self.printer + function_call_printer._expression_printer = self.printer + + request.cls.printer = self.printer + + def get_first_statement_in_update_block(self, model): + if model.get_model_list()[0].get_update_blocks()[0]: + return model.get_model_list()[0].get_update_blocks()[0].get_block().get_stmts()[0] + + return None + + def get_first_declaration_in_state_block(self, model): + assert len(model.get_model_list()[0].get_state_blocks()) == 1 + + return model.get_model_list()[0].get_state_blocks()[0].get_declarations()[0] + + def get_first_declared_function(self, model): + return model.get_model_list()[0].get_functions()[0] + + def print_rhs_of_first_assignment_in_update_block(self, model): + assignment = self.get_first_statement_in_update_block(model).small_stmt.get_assignment() + expression = assignment.get_expression() + + return self.printer.print(expression) + + def print_first_function_call_in_update_block(self, model): + function_call = self.get_first_statement_in_update_block(model).small_stmt.get_function_call() + + return self.printer.print(function_call) + + def print_rhs_of_first_declaration_in_state_block(self, model): + declaration = self.get_first_declaration_in_state_block(model) + expression = declaration.get_expression() + + return self.printer.print(expression) + + def print_first_return_statement_in_first_declared_function(self, model): + func = self.get_first_declared_function(model) + return_expression = func.get_block().get_stmts()[0].small_stmt.get_return_stmt().get_expression() + return self.printer.print(return_expression) + + def test_expression_after_magnitude_conversion_in_direct_assignment(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'DirectAssignmentWithDifferentButCompatibleUnits.nestml')) + printed_rhs_expression = self.print_rhs_of_first_assignment_in_update_block(model) + + assert printed_rhs_expression == '(1000.0 * (10 * V))' + + def test_expression_after_nested_magnitude_conversion_in_direct_assignment(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'DirectAssignmentWithDifferentButCompatibleNestedUnits.nestml')) + printed_rhs_expression = self.print_rhs_of_first_assignment_in_update_block(model) + + assert printed_rhs_expression == '(1000.0 * (10 * V + (0.001 * (5 * mV)) + 20 * V + (1000.0 * (1 * kV))))' + + def test_expression_after_magnitude_conversion_in_compound_assignment(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'CompoundAssignmentWithDifferentButCompatibleUnits.nestml')) + printed_rhs_expression = self.print_rhs_of_first_assignment_in_update_block(model) + + assert printed_rhs_expression == '(0.001 * (1200 * mV))' + + def test_expression_after_magnitude_conversion_in_declaration(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'DeclarationWithDifferentButCompatibleUnitMagnitude.nestml')) + printed_rhs_expression = self.print_rhs_of_first_declaration_in_state_block(model) + + assert printed_rhs_expression == '(1000.0 * (10 * V))' + + def test_expression_after_type_conversion_in_declaration(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'DeclarationWithDifferentButCompatibleUnits.nestml')) + declaration = self.get_first_declaration_in_state_block(model) + from astropy import units as u + + assert declaration.get_expression().type.unit.unit == u.mV + + def test_declaration_with_same_variable_name_as_unit(self): + Logger.init_logger(LoggingLevel.DEBUG) + + generate_target(input_path=os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'DeclarationWithSameVariableNameAsUnit.nestml'), target_platform="NONE", logging_level="DEBUG") + + assert len(Logger.get_all_messages_of_level_and_or_node("BlockTest", LoggingLevel.ERROR)) == 0 + assert len(Logger.get_all_messages_of_level_and_or_node("BlockTest", LoggingLevel.WARNING)) == 3 + + def test_expression_after_magnitude_conversion_in_standalone_function_call(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'FunctionCallWithDifferentButCompatibleUnits.nestml')) + printed_function_call = self.print_first_function_call_in_update_block(model) + + assert printed_function_call == 'foo((1000.0 * (10 * V)))' + + def test_expression_after_magnitude_conversion_in_rhs_function_call(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'RhsFunctionCallWithDifferentButCompatibleUnits.nestml')) + printed_function_call = self.print_rhs_of_first_assignment_in_update_block(model) + + assert printed_function_call == 'foo((1000.0 * (10 * V)))' + + def test_return_stmt_after_magnitude_conversion_in_function_body(self): + model = ModelParser.parse_file(os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), 'FunctionBodyReturnStatementWithDifferentButCompatibleUnits.nestml')) + printed_return_stmt = self.print_first_return_statement_in_first_declared_function(model) + + assert printed_return_stmt == '(0.001 * (bar))' diff --git a/tests/unit_system_test.py b/tests/unit_system_test.py deleted file mode 100644 index 1f7817b91..000000000 --- a/tests/unit_system_test.py +++ /dev/null @@ -1,177 +0,0 @@ -# -*- coding: utf-8 -*- -# -# unit_system_test.py -# -# This file is part of NEST. -# -# Copyright (C) 2004 The NEST Initiative -# -# NEST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# NEST is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with NEST. If not, see . - -import os -import unittest -from pynestml.codegeneration.printers.constant_printer import ConstantPrinter - -from pynestml.codegeneration.printers.cpp_expression_printer import CppExpressionPrinter -from pynestml.codegeneration.printers.cpp_simple_expression_printer import CppSimpleExpressionPrinter -from pynestml.codegeneration.printers.cpp_type_symbol_printer import CppTypeSymbolPrinter -from pynestml.codegeneration.printers.nestml_variable_printer import NestMLVariablePrinter -from pynestml.symbol_table.symbol_table import SymbolTable -from pynestml.symbols.predefined_functions import PredefinedFunctions -from pynestml.symbols.predefined_types import PredefinedTypes -from pynestml.symbols.predefined_units import PredefinedUnits -from pynestml.symbols.predefined_variables import PredefinedVariables -from pynestml.utils.ast_source_location import ASTSourceLocation -from pynestml.codegeneration.printers.cpp_variable_printer import CppVariablePrinter -from pynestml.codegeneration.printers.nest_cpp_function_call_printer import NESTCppFunctionCallPrinter -from pynestml.codegeneration.printers.cpp_function_call_printer import CppFunctionCallPrinter -from pynestml.utils.logger import Logger, LoggingLevel -from pynestml.utils.model_parser import ModelParser - - -SymbolTable.initialize_symbol_table(ASTSourceLocation(start_line=0, start_column=0, end_line=0, end_column=0)) - -PredefinedUnits.register_units() -PredefinedTypes.register_types() -PredefinedVariables.register_variables() -PredefinedFunctions.register_functions() - -Logger.init_logger(LoggingLevel.INFO) - -type_symbol_printer = CppTypeSymbolPrinter() -variable_printer = NestMLVariablePrinter(None) -function_call_printer = NESTCppFunctionCallPrinter(None) -cpp_variable_printer = CppVariablePrinter(None) -printer = CppExpressionPrinter(CppSimpleExpressionPrinter(cpp_variable_printer, - ConstantPrinter(), - function_call_printer)) -cpp_variable_printer._expression_printer = printer -variable_printer._expression_printer = printer -function_call_printer._expression_printer = printer - - -def get_first_statement_in_update_block(model): - if model.get_model_list()[0].get_update_blocks()[0]: - return model.get_model_list()[0].get_update_blocks()[0].get_block().get_stmts()[0] - return None - - -def get_first_declaration_in_state_block(model): - assert len(model.get_model_list()[0].get_state_blocks()) == 1 - return model.get_model_list()[0].get_state_blocks()[0].get_declarations()[0] - - -def get_first_declared_function(model): - return model.get_model_list()[0].get_functions()[0] - - -def print_rhs_of_first_assignment_in_update_block(model): - assignment = get_first_statement_in_update_block(model).small_stmt.get_assignment() - expression = assignment.get_expression() - return printer.print(expression) - - -def print_first_function_call_in_update_block(model): - function_call = get_first_statement_in_update_block(model).small_stmt.get_function_call() - return printer.print(function_call) - - -def print_rhs_of_first_declaration_in_state_block(model): - declaration = get_first_declaration_in_state_block(model) - expression = declaration.get_expression() - return printer.print(expression) - - -def print_first_return_statement_in_first_declared_function(model): - func = get_first_declared_function(model) - return_expression = func.get_block().get_stmts()[0].small_stmt.get_return_stmt().get_expression() - return printer.print(return_expression) - - -class UnitSystemTest(unittest.TestCase): - """ - Test class for everything Unit related. - """ - - def setUp(self): - Logger.set_logging_level(LoggingLevel.INFO) - - def test_expression_after_magnitude_conversion_in_direct_assignment(self): - Logger.set_logging_level(LoggingLevel.INFO) - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'DirectAssignmentWithDifferentButCompatibleUnits.nestml')) - printed_rhs_expression = print_rhs_of_first_assignment_in_update_block(model) - - self.assertEqual(printed_rhs_expression, '(1000.0 * (10 * V))') - - def test_expression_after_nested_magnitude_conversion_in_direct_assignment(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'DirectAssignmentWithDifferentButCompatibleNestedUnits.nestml')) - printed_rhs_expression = print_rhs_of_first_assignment_in_update_block(model) - - self.assertEqual(printed_rhs_expression, '(1000.0 * (10 * V + (0.001 * (5 * mV)) + 20 * V + (1000.0 * (1 * kV))))') - - def test_expression_after_magnitude_conversion_in_compound_assignment(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'CompoundAssignmentWithDifferentButCompatibleUnits.nestml')) - printed_rhs_expression = print_rhs_of_first_assignment_in_update_block(model) - self.assertEqual(printed_rhs_expression, '(0.001 * (1200 * mV))') - - def test_expression_after_magnitude_conversion_in_declaration(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'DeclarationWithDifferentButCompatibleUnitMagnitude.nestml')) - printed_rhs_expression = print_rhs_of_first_declaration_in_state_block(model) - self.assertEqual(printed_rhs_expression, '(1000.0 * (10 * V))') - - def test_expression_after_type_conversion_in_declaration(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'DeclarationWithDifferentButCompatibleUnits.nestml')) - declaration = get_first_declaration_in_state_block(model) - from astropy import units as u - self.assertTrue(declaration.get_expression().type.unit.unit == u.mV) - - def test_declaration_with_same_variable_name_as_unit(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'DeclarationWithSameVariableNameAsUnit.nestml')) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.ERROR)), 0) - self.assertEqual(len( - Logger.get_all_messages_of_level_and_or_node(model.get_model_list()[0], LoggingLevel.WARNING)), 3) - - def test_expression_after_magnitude_conversion_in_standalone_function_call(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'FunctionCallWithDifferentButCompatibleUnits.nestml')) - printed_function_call = print_first_function_call_in_update_block(model) - self.assertEqual(printed_function_call, 'foo((1000.0 * (10 * V)))') - - def test_expression_after_magnitude_conversion_in_rhs_function_call(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'RhsFunctionCallWithDifferentButCompatibleUnits.nestml')) - printed_function_call = print_rhs_of_first_assignment_in_update_block(model) - self.assertEqual(printed_function_call, 'foo((1000.0 * (10 * V)))') - - def test_return_stmt_after_magnitude_conversion_in_function_body(self): - model = ModelParser.parse_file( - os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), 'resources')), - 'FunctionBodyReturnStatementWithDifferentButCompatibleUnits.nestml')) - printed_return_stmt = print_first_return_statement_in_first_declared_function(model) - self.assertEqual(printed_return_stmt, '(0.001 * (bar))') diff --git a/tests/valid/CoCoConvolveNotCorrectlyParametrized.nestml b/tests/valid/CoCoConvolveNotCorrectlyParametrized.nestml index c4a6a213a..820002928 100644 --- a/tests/valid/CoCoConvolveNotCorrectlyParametrized.nestml +++ b/tests/valid/CoCoConvolveNotCorrectlyParametrized.nestml @@ -6,7 +6,7 @@ CoCoConvolveNotCorrectlyParametrized.nestml Description +++++++++++ -This model is used to test if broken CoCos are identified correctly. Here, if convolve has been correctly provided with a state block defined variable and a spike input port. +This model is used to test if broken CoCos are identified correctly. Here, if convolve has been correctly provided with a kernel and a spike input port. Positive case. @@ -35,9 +35,6 @@ model CoCoConvolveNotCorrectlyParametrized: parameters: tau ms = 20 ms - state: - G real = 1. - equations: kernel G' = -G / tau inline testB pA = convolve(G, spikeExc) * pA # convolve is now correctly parametrized diff --git a/tests/valid/CoCoInitValuesWithoutOde.nestml b/tests/valid/CoCoInitValuesWithoutOde.nestml index af506b410..6a5c71fa8 100644 --- a/tests/valid/CoCoInitValuesWithoutOde.nestml +++ b/tests/valid/CoCoInitValuesWithoutOde.nestml @@ -38,3 +38,6 @@ model CoCoInitValuesWithoutOde: equations: V_m' = 20mV / s # initial values provided with odes, thus everything is correct + + update: + integrate_odes()