diff --git a/doc/internals/mechanism_abi.rst b/doc/internals/mechanism_abi.rst index 972204b4c5f1f103344cbc525d679f25101f39b8..19ee56bfdeec0de8799887bf613861965a99c427 100644 --- a/doc/internals/mechanism_abi.rst +++ b/doc/internals/mechanism_abi.rst @@ -3,144 +3,273 @@ Mechanism ABI ============= -Here you will find the information needed to connect Arbor to mechanism -implementations outside the use of NMODL and ``modcc``. This may include writing -a custom compiler targetting Arbor, or directly implementing mechanisms in a -C-compatible language. Needless to say that this is aimed at developers rather -than users. +Code describing membrane mechanisms can be implemented in 3 ways: + +* custom C++/CUDA compiled into your Arbor program; +* NMODL, compiled through Arbor's ``modcc`` compiler; +* custom code exposed through the Arbor Mechanism Application Binary Interface (ABI). -The Arbor library is isolated from these implementations through an Application -Binary Interface (ABI) or plugin interface. Information is provided by the ABI -implementor via two core types. +Here you will find the information needed to connect Arbor to mechanism +implementations through the mechanism ABI. This ABI makes use of the +C type calling convection, so any language that can expose itself through a +`C FFI <https://en.wikipedia.org/wiki/Foreign_function_interface>`_ +can be used to implement mechanisms. The advantage of this method is that you can write +and compile mechanisms in a multitude of languages, and store and distribute those mechanism libraries +in compiled and optimized form. Any Arbor application can use them without compilation, of course +assuming that both the mechanism and Arbor are compiled for the same target platform. All functionality is offered via a single C header file in the Arbor include directory, ``mechanism_abi.h``. The central datatypes here are -``arb_mechanism_type`` and ``arb_mechanism_interface``, laying out the metadata -and backend implementations respectively. A single ``arb_mechanism_type`` -instance may be used by multiple ``arb_mechanism_interface`` instances. +:c:struct:`arb_mechanism_type` and :c:struct:`arb_mechanism_interface`, laying out the metadata +and backend implementations respectively. A single :c:struct:`arb_mechanism_type` +instance may be used by multiple :c:struct:`arb_mechanism_interface` instances. -Note that ``mechanism_abi.h`` is heavily commented and might be useful as -documentation in its own right. +.. Note:: + Note that ``mechanism_abi.h`` is heavily commented and is useful as + documentation in its own right, also when writing mechanisms in other languages than C(++). Metadata: ``arb_mechanism_type`` -------------------------------- This type collects all information independent of the backend. - .. code:: c - - typedef struct { - // Metadata - unsigned long abi_version; // mechanism was built using this ABI, - // should be ARB_MECH_ABI_VERSION - arb_mechanism_fingerprint fingerprint; // unique ID, currently ignored - const char* name; // (catalogue-level) unique name - arb_mechanism_kind kind; // one of: point, density, reversal_potential - bool is_linear; // synapses only: if the state G is governed by dG/dt = f(v, G, M(t)), where: - // M(t) =Σ wᵢδᵢ(t) weighted incoming events, - // then f is linear in G and M. If true, mechanisms must adhere to this contract. - // Ignored for everything else. - bool has_post_events; // implements post_event hook - // Tables - arb_field_info* globals; - arb_size_type n_globals; - arb_field_info* state_vars; - arb_size_type n_state_vars; - arb_field_info* parameters; - arb_size_type n_parameters; - arb_ion_info* ions; - arb_size_type n_ions; - } arb_mechanism_type; +.. c:struct:: arb_mechanism_type + + This type collects all information independent of the backend. + + Metadata: + + .. c:member:: unsigned long abi_version + + mechanism was built using this ABI, + should be ARB_MECH_ABI_VERSION + + .. c:member:: arb_mechanism_fingerprint fingerprint + + unique ID, currently ignored + + .. c:member:: const char* name + + (catalogue-level) unique name + + .. c:member:: arb_mechanism_kind kind + + one of: + + * point + * density + * reversal_potential + + .. c:member:: bool is_linear + + synapses only: if the state G is governed by dG/dt = f(v, G, M(t)), where: + M(t) =Σ wᵢδᵢ(t) weighted incoming events, + then f is linear in G and M. + + If true, mechanisms must adhere to this contract. + + Ignored for everything else. + + .. c:member:: bool has_post_events + + implements post_event hook + + Tables: + + .. c:member:: arb_field_info* globals + .. c:member:: arb_size_type n_globals + .. c:member:: arb_field_info* state_vars + .. c:member:: arb_size_type n_state_vars + .. c:member:: arb_field_info* parameters + .. c:member:: arb_size_type n_parameters + .. c:member:: arb_ion_info* ions + .. c:member:: arb_size_type n_ions Tables '''''' -All tables are given as an integer size and an array. Currently we have two -kinds of tables, which are fairly self-explanatory. Note that these are not -connected to the actual storage layout, in particular, no memory management is -allowed inside mechanisms. +All tables are given as an integer size and an array. There are two +kinds of tables: :c:struct:`arb_field_info` and :c:struct:`arb_ion_info`. +:c:struct:`arb_field_info` holds parameters, state variables, and global constants; +:c:struct:`arb_ion_info` holds ion dependencies. + +.. c:struct:: arb_field_info + + .. c:member:: const char* name + + Field name, can be used from Arbor to query/set field values. + + .. c:member:: const char* unit + + Physical units, just for introspection, not checked + + .. c:member:: arb_value_type default_value -First, parameters, state variables, and global constants + values will be initialised to this value - .. code:: c + .. c:member:: arb_value_type range_low - typedef struct { - const char* name; // Field name, can be used from the library to query/set field values. - const char* unit; // Physical units, just for introspection, not checked - arb_value_type default_value; // values will be initialised to this value - arb_value_type range_low; // valid range, lower bound, will be enforced - arb_value_type range_high; // valid range, upper bound, will be enforced - } arb_field_info; + valid range, lower bound, will be enforced -Second ion dependencies + .. c:member:: arb_value_type range_high - .. code:: c + valid range, upper bound, will be enforced - typedef struct { - const char* name; // Ion name, eg Ca, K, ... - bool write_int_concentration; // writes Xi? - bool write_ext_concentration; // writes Xo? - bool write_rev_potential; // writes Er? - bool read_rev_potential; // uses Er? - bool read_valence; // Uses valence? - bool verify_valence; // Checks valence? - int expected_valence; // Expected value - } arb_ion_info; +.. c:struct:: arb_ion_info + + .. c:member:: const char* name + + Ion name, eg Ca, K, ... + + .. c:member:: bool write_int_concentration + + writes Xi? + + .. c:member:: bool write_ext_concentration + + writes Xo? + + .. c:member:: bool write_rev_potential + + writes Er? + + .. c:member:: bool read_rev_potential + + uses Er? + + .. c:member:: bool read_valence + + Uses valence? + + .. c:member:: bool verify_valence + + Checks valence? + + .. c:member:: int expected_valence + + Expected value Interlude: Parameter packs -------------------------- -In order to explain the interface type, we have to digress first and introduce -the type ``arb_mechanism_ppack``. This record is used to pass all information to +In order to explain the interface type, we have to introduce +the type :c:struct:`arb_mechanism_ppack`. This record is used to pass all information to and from the interface methods. -Objects of this type are always created and allocated by the library and passed -fully formed to the interface. In particular, at this point +Objects of this type are always created and allocated by Arbor and passed +fully formed to the interface. At this point: - Global data values are initialised - pointers in ``ion_state_view`` are set to their associated values in shared - state on the library side + state on the Arbor side - pointers to state, parameters, globals, and constants are allocated and initialised to the given defaults. -- SIMD only: ``index_constraint`` is set up - - .. code:: c - - typedef struct { - // Global data - arb_index_type width; // Number of CVs of this mechanism, size of arrays - arb_index_type n_detectors; // Number of spike detectors - arb_index_type* vec_ci; // [Array] Map CV to cell - arb_index_type* vec_di; // [Array] Map - const arb_value_type* vec_t; // [Array] time value - arb_value_type* vec_dt; // [Array] time step - arb_value_type* vec_v; // [Array] potential - arb_value_type* vec_i; // [Array] current - arb_value_type* vec_g; // [Array] conductance - arb_value_type* temperature_degC; // [Array] Temperature in celsius - arb_value_type* diam_um; // [Array] CV diameter - arb_value_type* time_since_spike; // Times since last spike; one entry per cell and detector. - arb_index_type* node_index; // Indices of CVs covered by this mechanism, size is width - arb_index_type* multiplicity; // [Unused] - arb_value_type* weight; // [Array] Weight - arb_size_type mechanism_id; // Unique ID for this mechanism on this cell group - arb_deliverable_event_stream events; // Events during the last period - arb_constraint_partition index_constraints; // Index restrictions, not initialised for all backends. - // User data - arb_value_type** parameters; // [Array] setable parameters - arb_value_type** state_vars; // [Array] integrable state - arb_value_type* globals; // global constant state - arb_ion_state* ion_states; // [Array] views into shared state - } arb_mechanism_ppack; +- SIMD only: :c:member:`arb_mechanism_ppack.index_constraints` is set up. + +.. c:struct:: arb_mechanism_ppack + + Global data: + + .. c:member:: arb_index_type width + + Number of CVs of this mechanism, size of arrays + + .. c:member:: arb_index_type n_detectors + + Number of spike detectors + + .. c:member:: arb_index_type* vec_ci + + [Array] Map CV to cell + + .. c:member:: arb_index_type* vec_di + + [Array] Map + + .. c:member:: const arb_value_type* vec_t + + [Array] time value + + .. c:member:: arb_value_type* vec_dt + + [Array] time step + + .. c:member:: arb_value_type* vec_v + + [Array] potential + + .. c:member:: arb_value_type* vec_i + + [Array] current + + .. c:member:: arb_value_type* vec_g + + [Array] conductance + + .. c:member:: arb_value_type* temperature_degC + + [Array] Temperature in celsius + + .. c:member:: arb_value_type* diam_um + + [Array] CV diameter + + .. c:member:: arb_value_type* time_since_spike + + Times since last spike; one entry per cell and detector. + + .. c:member:: arb_index_type* node_index + + Indices of CVs covered by this mechanism, size is width + + .. c:member:: arb_index_type* multiplicity + + [Unused] + + .. c:member:: arb_value_type* weight + + [Array] Weight + + .. c:member:: arb_size_type mechanism_id + + Unique ID for this mechanism on this cell group + + .. c:member:: arb_deliverable_event_stream events + + Events during the last period + + .. c:member:: arb_constraint_partition index_constraints + + Index restrictions, not initialised for all backends. + + User data: + + .. c:member:: arb_value_type** parameters + + [Array] setable parameters + + .. c:member:: arb_value_type** state_vars + + [Array] integrable state + + .. c:member:: arb_value_type* globals + + global constant state + + .. c:member:: arb_ion_state* ion_states + + [Array] views into shared state Members tagged as ``[Array]`` represent one value per CV. To access the values -belonging to your mechanism, a level of indirection via ``node_index`` is +belonging to your mechanism, a level of indirection via :c:member:`arb_mechanism_ppack.node_index` is needed. -Example: Let's assume mechanism ``hh`` is defined on two regions: ``R`` -comprising CVs ``0`` and ``1``, ``R'`` with a single CV ``9``. Then ``node_index -= [0, 1, 9]`` and ``width = 3``. Arrays like ``vec_v`` will be of size ``3`` as -well. To access the CVs' diameters, one would write +.. admonition:: Example + + Let's assume mechanism ``hh`` is defined on two regions: ``R`` + comprising CVs ``0`` and ``1``, ``R'`` with a single CV ``9``. Then ``node_index + = [0, 1, 9]`` and ``width = 3``. Arrays like ``vec_v`` will be of size ``3`` as + well. To access the CVs' diameters, one would write: .. code:: c++ @@ -149,10 +278,11 @@ well. To access the CVs' diameters, one would write auto d = ppack_um[idx]; } -Note that values in ``ppack.diam_um`` cover _all_ CV's regardless whether they -are covered by the current mechanisms. Reading those values (or worse writing to -them) is considered undefined behaviour. The same holds for all other fields of -``ppack``. +.. warning:: + Note that values in :c:member:`arb_mechanism_ppack.diam_um` cover _all_ CV's regardless whether they + are covered by the current mechanisms. Reading or writing to those values + is considered undefined behaviour. The same holds for all other fields of + :c:struct:`arb_mechanism_ppack`. User Data ''''''''' @@ -160,19 +290,19 @@ User Data This section is derived from the tables passed in via the metadata struct, see above. One entry per relevant table entry is provided in the same order. So, if - .. code:: c +.. code:: c - arb_field_info globals[] = { arb_field_info { .name="A", - .unit="lb ft / s", - .default_value=42.0, - .range_low=0, - .range_high=123 }, - arb_field_info { .name="B", - .unit="kg m / s", - .default_value=42.0, - .range_low=0, - .range_high=123 }}; - arb_mechanism_type m = { .n_globals=2, .globals=globals }; + arb_field_info globals[] = { arb_field_info { .name="A", + .unit="lb ft / s", + .default_value=42.0, + .range_low=0, + .range_high=123 }, + arb_field_info { .name="B", + .unit="kg m / s", + .default_value=42.0, + .range_low=0, + .range_high=123 }}; + arb_mechanism_type m = { .n_globals=2, .globals=globals }; the ``globals`` field of the corresponding parameter pack would have two entries, the first corresponding to ``A`` and initialised to 42.0 and the second @@ -192,119 +322,130 @@ to a backend. Implementation: ``arb_mechanism_interface`` ------------------------------------------- -The interface methods will be called with allocated and initialised ``ppack`` +The interface methods will be called with allocated and initialised :c:struct:`arb_mechanism_ppack` data. The actual layout is unspecified, but all pointers are allocated and set -by the library. Plugins should never allocate memory on their own. - - .. code:: C +by Arbor. This means that your code must read/write to these locations in memory, +and that you cannot change the pointer to point to another slice of memory allocated +by your code. - typedef void (*arb_mechanism_method)(arb_mechanism_ppack*); +.. c:type:: void (*arb_mechanism_method)(arb_mechanism_ppack*); This is the type of all interface methods. These are collected in the record below with some metadata about the backend. - .. code:: C +.. c:struct:: arb_mechanism_interface + + .. c:member:: arb_backend_kind backend + + one of + + - cpu + - gpu + + .. c:member:: arb_size_type partition_width + + granularity for this backed, eg SIMD lanes - typedef struct { - arb_backend_kind backend; // one of cpu, gpu - arb_size_type partition_width; // granularity for this backed, eg SIMD lanes - // Interface methods; see below - arb_mechanism_method init_mechanism; - arb_mechanism_method compute_currents; - arb_mechanism_method apply_events; - arb_mechanism_method advance_state; - arb_mechanism_method write_ions; - arb_mechanism_method post_event; - } arb_mechanism_interface; + Interface methods: + .. c:member:: arb_mechanism_method init_mechanism -``init_mechanism`` -'''''''''''''''''' -- called once during instantiation, -- setup initial state, corresponds to NMODL's INITIAL block, -- will receive an allocated and initialised ppack object + - called once during instantiation, + - setup initial state, corresponds to NMODL's INITIAL block, + - will receive an allocated and initialised ppack object -``compute_currents`` -'''''''''''''''''''' + .. c:member:: arb_mechanism_method compute_currents -- compute ionic currents and set them through pointers in `ion_state`, currents - live in `current_density` -- called during each integration time step - - at the start for reversal potential mechanisms, *before* current reset - - after event deliver for anything else + - compute ionic currents and set them through pointers in `ion_state`, currents + live in `current_density` + - called during each integration time step + - at the start for reversal potential mechanisms, *before* current reset + - after event deliver for anything else -``apply_events`` -'''''''''''''''' + .. c:member:: arb_mechanism_method apply_events -This method is expected to consume a set of `arb_deliverable_events` and apply -effects to internal state, found in ``ppack.events`` which is of type -``arb_deliverable_event_stream``. + This method is expected to consume a set of :c:struct:`arb_deliverable_event` and apply + effects to internal state, found in :c:member:`arb_mechanism_ppack.events` which is of type + :c:struct:`arb_deliverable_event_stream`. - .. code:: c + These structures are set up correctly externally, but are only valid during this call. + The data is read-only for :c:member:`arb_mechanism_interface.apply_events`. - typedef struct { - arb_size_type mech_id; // mechanism type identifier (per cell group). - arb_size_type mech_index; // instance of the mechanism - arb_float_type weight; // connection weight - } arb_deliverable_event; + - called during each integration time step, right after resetting currents + - corresponding to ``NET_RECEIVE`` - typedef struct { - arb_size_type n_streams; // number of streams - const arb_deliverable_event* events; // array of event data items - const arb_index_type* begin; // array of offsets to beginning of marked events - const arb_index_type* end; // array of offsets to end of marked events - } arb_deliverable_event_stream; + .. c:member:: arb_mechanism_method advance_state -These structures are set up correctly externally, but are only valid during this call. -The data is read-only for ``apply_events``. + - called during each integration time step, after solving Hines matrices + - perform integration on state variables + - state variables live in `state_vars`, with a layout described above -- called during each integration time step, right after resetting currents -- corresponding to ``NET_RECEIVE`` + .. c:member:: arb_mechanism_method write_ions -``advanced_state`` -'''''''''''''''''' + - update ionic concentrations via the pointers in `ion_state` + - called during each integration time step, after state integration -- called during each integration time step, after solving Hines matrices -- perform integration on state variables -- state variables live in `state_vars`, with a layout described above + .. c:member:: arb_mechanism_method post_event -``write_ions`` -'''''''''''''' + - used to implement spike time dependent plasticity + - consumes :c:member:`arb_mechanism_ppack.time_since_spike` + - called during each integration time step, after checking for spikes + - if implementing this, also set :c:member:`arb_mechanism_type.has_post_events` to ``true`` in the metadata -- update ionic concentrations via the pointers in `ion_state` -- called during each integration time step, after state integration +.. c:struct:: arb_deliverable_event -``post_event`` -'''''''''''''' + .. c:member:: arb_size_type mech_id -- used to implement spike time dependent plasticity -- consumes ``ppack.time_since_spike`` -- called during each integration time step, after checking for spikes -- if implementing this, also set ``has_post_event=true`` in the metadata + mechanism type identifier (per cell group). + + .. c:member:: arb_size_type mech_index + + instance of the mechanism + + .. c:member:: arb_float_type weight + + connection weight + +.. c:struct:: arb_deliverable_event_stream + + .. c:member:: arb_size_type n_streams + + number of streams + + .. c:member:: const arb_deliverable_event* events + + array of event data items + + .. c:member:: const arb_index_type* begin + + array of offsets to beginning of marked events + + .. c:member:: const arb_index_type* end + + array of offsets to end of marked events SIMDization ----------- If a mechanism interface processes arrays in SIMD bundles, it needs to set -``partition_width`` to that bundle's width in units of ``arb_value_type``. The -library will set up ``arb_constraint_partition index_constraint`` in the -parameter pack. This structure describe which bundles can be loaded/stored as a +:c:member:`arb_mechanism_interface.partition_width` to that bundle's width in units of ``arb_value_type``. The +library will set up :c:member:`arb_mechanism_ppack.index_constraints` in the +parameter pack. This structure describes which bundles can be loaded/stored as a contiguous block, which ones must be gathered/scattered, which are to be broadcast from a constant, and so on. The reason for this is the indirection via -``node_index`` mentioned before. Please refer to the documentation of our SIMD -interface layer for more information. +:c:member:`arb_mechanism_ppack.node_index` mentioned before. Please refer to the documentation of our :ref:`SIMD +interface layer <simd>` for more information. Making A Loadable Mechanism --------------------------- -Mechanisms interface with the library by providing three functions, one +Mechanisms interface with Arbor by providing three functions, one returning the metadata portion, and one for each implemented backend (currently two). The latter may return a NULL pointer, indicating that this backend is not supported. The naming scheme is shown in the example below - .. code:: C - - arb_mechanism_type make_arb_default_catalogue_pas(); +.. code:: C - arb_mechanism_interface* make_arb_default_catalogue_pas_interface_multicore(); - arb_mechanism_interface* make_arb_default_catalogue_pas_interface_gpu(); + arb_mechanism_type make_arb_default_catalogue_pas(); + arb_mechanism_interface* make_arb_default_catalogue_pas_interface_multicore(); + arb_mechanism_interface* make_arb_default_catalogue_pas_interface_gpu(); diff --git a/doc/internals/simd_api.rst b/doc/internals/simd_api.rst index 3d22af1f9ab388ee2c5675e0b8d175633eb28d74..1b315a6184615fc1ed6d3a979d7f8cf2b8e1f6e9 100644 --- a/doc/internals/simd_api.rst +++ b/doc/internals/simd_api.rst @@ -1,3 +1,5 @@ +.. _simd: + SIMD Classes ============ diff --git a/doc/scripts/divio_docs_theme/static/css/divio.css b/doc/scripts/divio_docs_theme/static/css/divio.css index 1e4562d3b7e61aecee9396011926d3e7863ae936..1f2bf56c54ffccd6d62af58bd53cbebc927ac118 100644 --- a/doc/scripts/divio_docs_theme/static/css/divio.css +++ b/doc/scripts/divio_docs_theme/static/css/divio.css @@ -3717,46 +3717,64 @@ code, .rst-content tt, .rst-content code { code.code-large, .rst-content tt.code-large { font-size: 90%; } -.wy-plain-list-disc, .rst-content section ul, .rst-content .toctree-wrapper ul, article ul { +.wy-plain-list-disc, .rst-content section ul, .rst-content .toctree-wrapper ul { list-style: disc; line-height: 24px; margin-bottom: 24px; } -.wy-plain-list-disc li, .rst-content section ul li, .rst-content .toctree-wrapper ul li, article ul li { +.wy-plain-list-disc li, .rst-content section ul li, .rst-content .toctree-wrapper ul li { list-style: disc; margin-left: 24px; } -.wy-plain-list-disc li p:last-child, .rst-content section ul li p:last-child, .rst-content .toctree-wrapper ul li p:last-child, article ul li p:last-child { +.wy-plain-list-disc li p:last-child, .rst-content section ul li p:last-child, .rst-content .toctree-wrapper ul li p:last-child { margin-bottom: 0; } -.wy-plain-list-disc li ul, .rst-content section ul li ul, .rst-content .toctree-wrapper ul li ul, article ul li ul { +.wy-plain-list-disc li ul, .rst-content section ul li ul, .rst-content .toctree-wrapper ul li ul { margin-bottom: 0; } -.wy-plain-list-disc li li, .rst-content section ul li li, .rst-content .toctree-wrapper ul li li, article ul li li { +.wy-plain-list-disc li li, .rst-content section ul li li, .rst-content .toctree-wrapper ul li li { list-style: circle; } -.wy-plain-list-disc li li li, .rst-content section ul li li li, .rst-content .toctree-wrapper ul li li li, article ul li li li { +.wy-plain-list-disc li li li, .rst-content section ul li li li, .rst-content .toctree-wrapper ul li li li { list-style: square; } -.wy-plain-list-disc li ol li, .rst-content section ul li ol li, .rst-content .toctree-wrapper ul li ol li, article ul li ol li { +.wy-plain-list-disc li ol li, .rst-content section ul li ol li, .rst-content .toctree-wrapper ul li ol li { list-style: decimal; } -.wy-plain-list-decimal, .rst-content section ol, .rst-content ol.arabic, article ol { +.wy-plain-list-decimal, .rst-content section ol, .rst-content ol.arabic { list-style: decimal; line-height: 24px; margin-bottom: 24px; } -.wy-plain-list-decimal li, .rst-content section ol li, .rst-content ol.arabic li, article ol li { +.wy-plain-list-decimal li, .rst-content section ol li, .rst-content ol.arabic li { list-style: decimal; margin-left: 24px; } -.wy-plain-list-decimal li p:last-child, .rst-content section ol li p:last-child, .rst-content ol.arabic li p:last-child, article ol li p:last-child { +.wy-plain-list-decimal li p:last-child, .rst-content section ol li p:last-child, .rst-content ol.arabic li p:last-child { margin-bottom: 0; } -.wy-plain-list-decimal li ul, .rst-content section ol li ul, .rst-content ol.arabic li ul, article ol li ul { +.wy-plain-list-decimal li ul, .rst-content section ol li ul, .rst-content ol.arabic li ul { margin-bottom: 0; } -.wy-plain-list-decimal li ul li, .rst-content section ol li ul li, .rst-content ol.arabic li ul li, article ol li ul li { +.wy-plain-list-decimal li ul li, .rst-content section ol li ul li, .rst-content ol.arabic li ul li { + list-style: disc; } + +.wy-plain-list-disc, .rst-content section ul, .rst-content ul { + list-style: disc; + line-height: 24px; + margin-bottom: 24px; } + +.wy-plain-list-disc li, .rst-content section ul li, .rst-content ul li { + list-style: disc; + margin-left: 24px; } + +.wy-plain-list-disc li p:last-child, .rst-content section ul li p:last-child, .rst-content ul li p:last-child { + margin-bottom: 0; } + +.wy-plain-list-disc li ul, .rst-content section ul li ul, .rst-content ul li ul { + margin-bottom: 0; } + +.wy-plain-list-disc li ul li, .rst-content section ul li ul li, .rst-content ul li ul li { list-style: disc; } .codeblock-example { @@ -3825,9 +3843,9 @@ div[class^='highlight'] pre { padding: 0 12px; display: block; } -.c { +/* .c { color: #999988; - font-style: italic; } + font-style: italic; } */ .err { color: #a61717;