dendrotweaks 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dendrotweaks/__init__.py +2 -2
- dendrotweaks/analysis/ephys_analysis.py +12 -8
- dendrotweaks/biophys/__init__.py +7 -0
- dendrotweaks/{membrane → biophys}/default_templates/NEURON_template.py +5 -3
- dendrotweaks/{membrane → biophys}/default_templates/default.py +2 -1
- dendrotweaks/{membrane → biophys}/default_templates/standard_channel.mod +5 -1
- dendrotweaks/{membrane → biophys}/groups.py +2 -2
- dendrotweaks/biophys/io/__init__.py +11 -0
- dendrotweaks/{membrane → biophys}/io/ast.py +6 -0
- dendrotweaks/{membrane → biophys}/io/code_generators.py +7 -1
- dendrotweaks/{membrane → biophys}/io/converter.py +3 -3
- dendrotweaks/{membrane → biophys}/io/factories.py +8 -6
- dendrotweaks/biophys/io/loader.py +190 -0
- dendrotweaks/{membrane → biophys}/io/parser.py +8 -8
- dendrotweaks/{membrane → biophys}/mechanisms.py +14 -10
- dendrotweaks/model.py +65 -32
- dendrotweaks/morphology/sec_trees.py +1 -1
- dendrotweaks/path_manager.py +9 -11
- dendrotweaks/simulators.py +82 -48
- dendrotweaks/stimuli/populations.py +11 -0
- {dendrotweaks-0.3.1.dist-info → dendrotweaks-0.4.0.dist-info}/METADATA +2 -2
- dendrotweaks-0.4.0.dist-info/RECORD +56 -0
- {dendrotweaks-0.3.1.dist-info → dendrotweaks-0.4.0.dist-info}/WHEEL +1 -1
- dendrotweaks/membrane/__init__.py +0 -6
- dendrotweaks/membrane/io/__init__.py +0 -11
- dendrotweaks/membrane/io/loader.py +0 -90
- dendrotweaks-0.3.1.dist-info/RECORD +0 -56
- /dendrotweaks/{membrane → biophys}/default_mod/AMPA.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/AMPA_NMDA.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/CaDyn.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/GABAa.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/Leak.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/NMDA.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_mod/vecstim.mod +0 -0
- /dendrotweaks/{membrane → biophys}/default_templates/template_jaxley.py +0 -0
- /dendrotweaks/{membrane → biophys}/default_templates/template_jaxley_new.py +0 -0
- /dendrotweaks/{membrane → biophys}/distributions.py +0 -0
- /dendrotweaks/{membrane → biophys}/io/grammar.py +0 -0
- /dendrotweaks/{membrane → biophys}/io/reader.py +0 -0
- {dendrotweaks-0.3.1.dist-info → dendrotweaks-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {dendrotweaks-0.3.1.dist-info → dendrotweaks-0.4.0.dist-info}/top_level.txt +0 -0
    
        dendrotweaks/__init__.py
    CHANGED
    
    | @@ -1,8 +1,8 @@ | |
| 1 | 
            -
            __version__ = "0. | 
| 1 | 
            +
            __version__ = "0.4.0"
         | 
| 2 2 |  | 
| 3 3 | 
             
            from dendrotweaks.model import Model
         | 
| 4 4 | 
             
            from dendrotweaks.simulators import NEURONSimulator
         | 
| 5 | 
            -
            from dendrotweaks. | 
| 5 | 
            +
            from dendrotweaks.biophys.distributions import Distribution
         | 
| 6 6 | 
             
            from dendrotweaks.path_manager import PathManager
         | 
| 7 7 | 
             
            from dendrotweaks.stimuli import Synapse, Population, IClamp
         | 
| 8 8 |  | 
| @@ -27,7 +27,7 @@ def get_somatic_data(model): | |
| 27 27 | 
             
                seg = model.seg_tree.root
         | 
| 28 28 | 
             
                iclamp = model.iclamps[seg]
         | 
| 29 29 |  | 
| 30 | 
            -
                v = np.array(model.simulator. | 
| 30 | 
            +
                v = np.array(model.simulator.recordings['v'][seg])
         | 
| 31 31 | 
             
                t = np.array(model.simulator.t)
         | 
| 32 32 | 
             
                dt = model.simulator.dt
         | 
| 33 33 |  | 
| @@ -165,7 +165,7 @@ def detect_somatic_spikes(model, **kwargs): | |
| 165 165 | 
             
                """
         | 
| 166 166 | 
             
                seg = model.seg_tree.root
         | 
| 167 167 |  | 
| 168 | 
            -
                v = np.array(model.simulator. | 
| 168 | 
            +
                v = np.array(model.simulator.recordings['v'][seg])
         | 
| 169 169 | 
             
                t = np.array(model.simulator.t)
         | 
| 170 170 | 
             
                dt = model.simulator.dt
         | 
| 171 171 |  | 
| @@ -271,7 +271,7 @@ def calculate_fI_curve(model, duration=1000, min_amp=0, max_amp=1, n=5, **kwargs | |
| 271 271 | 
             
                    n_spikes = len(spike_data['spike_times'])
         | 
| 272 272 | 
             
                    rate = n_spikes / iclamp.dur * 1000
         | 
| 273 273 | 
             
                    rates.append(rate)
         | 
| 274 | 
            -
                    vs[amp] = model.simulator. | 
| 274 | 
            +
                    vs[amp] = model.simulator.recordings['v'][seg]
         | 
| 275 275 |  | 
| 276 276 | 
             
                return {
         | 
| 277 277 | 
             
                    'current_amplitudes': amps,
         | 
| @@ -336,11 +336,14 @@ def calculate_voltage_attenuation(model): | |
| 336 336 | 
             
                if len(stimulated_segs) != 1:
         | 
| 337 337 | 
             
                    print("Only one stimulation site is supported")
         | 
| 338 338 | 
             
                    return None
         | 
| 339 | 
            -
                recorded_segs = list(model.recordings.keys())
         | 
| 339 | 
            +
                recorded_segs = list(model.recordings['v'].keys())
         | 
| 340 340 | 
             
                if len(recorded_segs) < 2:
         | 
| 341 341 | 
             
                    print("At least two recording sites are required")
         | 
| 342 342 | 
             
                    return None
         | 
| 343 343 |  | 
| 344 | 
            +
                print(f"Stimulating segment: {stimulated_segs[0]}")
         | 
| 345 | 
            +
                print(f"Recording segments: {recorded_segs}")
         | 
| 346 | 
            +
             | 
| 344 347 | 
             
                stimulated_seg = stimulated_segs[0]
         | 
| 345 348 |  | 
| 346 349 | 
             
                iclamp = model.iclamps[stimulated_seg]
         | 
| @@ -355,8 +358,9 @@ def calculate_voltage_attenuation(model): | |
| 355 358 | 
             
                start_ts = int(iclamp.delay / model.simulator.dt)
         | 
| 356 359 | 
             
                stop_ts = int((iclamp.delay + iclamp.dur) / model.simulator.dt)
         | 
| 357 360 |  | 
| 358 | 
            -
                voltage_at_stimulated = np.array(model.simulator. | 
| 359 | 
            -
                voltages = [np.array(model.simulator. | 
| 361 | 
            +
                voltage_at_stimulated = np.array(model.simulator.recordings['v'][stimulated_seg])[start_ts:stop_ts]
         | 
| 362 | 
            +
                voltages = [np.array(model.simulator.recordings['v'][seg])[start_ts:stop_ts] for seg in recorded_segs]
         | 
| 363 | 
            +
             | 
| 360 364 |  | 
| 361 365 | 
             
                # Calculate voltage displacement from the resting potential
         | 
| 362 366 | 
             
                delta_v_at_stimulated = voltage_at_stimulated[0] - np.min(voltage_at_stimulated)
         | 
| @@ -405,7 +409,7 @@ def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=No | |
| 405 409 | 
             
                    A dictionary containing the expected and observed voltage changes.
         | 
| 406 410 | 
             
                """
         | 
| 407 411 |  | 
| 408 | 
            -
                recorded_segs = list(model.recordings.keys())
         | 
| 412 | 
            +
                recorded_segs = list(model.recordings['v'].keys())
         | 
| 409 413 | 
             
                seg = recorded_segs[0]
         | 
| 410 414 |  | 
| 411 415 | 
             
                populations = [pop for pops in model.populations.values() for pop in pops.values()]
         | 
| @@ -432,7 +436,7 @@ def calculate_dendritic_nonlinearity(model, duration=1000, max_weight=None, n=No | |
| 432 436 | 
             
                for w in weights:
         | 
| 433 437 | 
             
                    population.update_input_params(weight=w)
         | 
| 434 438 | 
             
                    model.simulator.run(duration)
         | 
| 435 | 
            -
                    v = np.array(model.simulator. | 
| 439 | 
            +
                    v = np.array(model.simulator.recordings['v'][seg])
         | 
| 436 440 | 
             
                    v_start = v[start_ts]
         | 
| 437 441 | 
             
                    v_max = np.max(v[start_ts:])
         | 
| 438 442 | 
             
                    delta_v = v_max - v_start
         | 
| @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            from dendrotweaks.biophys.mechanisms import Mechanism, IonChannel, StandardIonChannel
         | 
| 2 | 
            +
            from dendrotweaks.biophys.mechanisms import CaDynamics, LeakChannel
         | 
| 3 | 
            +
            from dendrotweaks.biophys.mechanisms import LeakChannel
         | 
| 4 | 
            +
            from dendrotweaks.biophys.groups import SegmentGroup
         | 
| 5 | 
            +
            from dendrotweaks.biophys.distributions import Distribution
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            import dendrotweaks.biophys.io as io
         | 
| @@ -114,7 +114,7 @@ class Cell(): | |
| 114 114 | 
             
                    {% for domain, mechanisms in domains_to_mechs.items() %}
         | 
| 115 115 | 
             
                    for sec in self.{{ domains_to_NEURON[domain] }}:
         | 
| 116 116 | 
             
                        {% for mechanism in mechanisms %}
         | 
| 117 | 
            -
             | 
| 117 | 
            +
                        sec.insert('{{ mechanism }}')
         | 
| 118 118 | 
             
                        {%- endfor %}
         | 
| 119 119 | 
             
                    {% endfor %}
         | 
| 120 120 |  | 
| @@ -235,10 +235,12 @@ class Cell(): | |
| 235 235 |  | 
| 236 236 | 
             
                def add_recordings(self):
         | 
| 237 237 | 
             
                    recordings = []
         | 
| 238 | 
            -
                    {% for  | 
| 238 | 
            +
                    {% for var, recs in recordings.items() %}
         | 
| 239 | 
            +
                    {% for seg, rec in recs.items() %}
         | 
| 239 240 | 
             
                    rec = h.Vector()
         | 
| 240 | 
            -
                    rec.record(self.{{seg._section.domain}}[{{seg._section.domain_idx}}]({{seg.x}}). | 
| 241 | 
            +
                    rec.record(self.{{seg._section.domain}}[{{seg._section.domain_idx}}]({{seg.x}})._ref_{{ var }})
         | 
| 241 242 | 
             
                    recordings.append(rec)
         | 
| 243 | 
            +
                    {%- endfor -%}
         | 
| 242 244 | 
             
                    {% endfor %}
         | 
| 243 245 | 
             
                    return recordings
         | 
| 244 246 |  | 
| @@ -3,7 +3,7 @@ | |
| 3 3 |  | 
| 4 4 | 
             
            import sys
         | 
| 5 5 |  | 
| 6 | 
            -
            from dendrotweaks. | 
| 6 | 
            +
            from dendrotweaks.biophys.mechanisms import IonChannel
         | 
| 7 7 | 
             
            import numpy as np
         | 
| 8 8 |  | 
| 9 9 | 
             
            class {{ class_name }}(IonChannel):
         | 
| @@ -43,6 +43,7 @@ class {{ class_name }}(IonChannel): | |
| 43 43 | 
             
                    }
         | 
| 44 44 | 
             
                    self.ion = "{{ ion }}"
         | 
| 45 45 | 
             
                    self.current_name = "i_{{ ion }}"
         | 
| 46 | 
            +
                    self.current_available = {{ current_available }}
         | 
| 46 47 | 
             
                    self.independent_var_name = "{{ independent_var_name }}"
         | 
| 47 48 | 
             
                    self.temperature = 37
         | 
| 48 49 |  | 
| @@ -23,7 +23,7 @@ UNITS { | |
| 23 23 | 
             
            }
         | 
| 24 24 |  | 
| 25 25 | 
             
            PARAMETER {
         | 
| 26 | 
            -
                {% for key, value, unit in  | 
| 26 | 
            +
                {% for key, value, unit in params %}{{ "%-7s"|format(key) }} = {{ value }} ({{ unit }}){% if not loop.last %}
         | 
| 27 27 | 
             
                {% endif %}{% endfor %}
         | 
| 28 28 | 
             
            }
         | 
| 29 29 |  | 
| @@ -57,7 +57,11 @@ DERIVATIVE states { | |
| 57 57 | 
             
            }
         | 
| 58 58 |  | 
| 59 59 | 
             
            INITIAL {
         | 
| 60 | 
            +
                {%- if has_tadj%}
         | 
| 60 61 | 
             
                tadj = q10^((celsius - temp)/10(degC))
         | 
| 62 | 
            +
                {%- else %}
         | 
| 63 | 
            +
                tadj = 1
         | 
| 64 | 
            +
                {%- endif %}
         | 
| 61 65 | 
             
                rates(v)
         | 
| 62 66 | 
             
                {% for state in state_vars %}{{ state }} = {{ state }}_inf
         | 
| 63 67 | 
             
                {% endfor %}
         | 
| @@ -3,8 +3,8 @@ from typing import List, Callable, Dict | |
| 3 3 | 
             
            from dendrotweaks.morphology.trees import Node
         | 
| 4 4 | 
             
            from dendrotweaks.morphology.sec_trees import Section
         | 
| 5 5 | 
             
            from dendrotweaks.morphology.seg_trees import Segment
         | 
| 6 | 
            -
            from dendrotweaks. | 
| 7 | 
            -
            from dendrotweaks. | 
| 6 | 
            +
            from dendrotweaks.biophys.mechanisms import Mechanism
         | 
| 7 | 
            +
            from dendrotweaks.biophys.distributions import Distribution
         | 
| 8 8 | 
             
            from dendrotweaks.utils import timeit
         | 
| 9 9 | 
             
            from dataclasses import dataclass, field, asdict
         | 
| 10 10 | 
             
            from typing import List, Tuple, Dict, Optional
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            from dendrotweaks.biophys.io.loader import MODFileLoader
         | 
| 2 | 
            +
            from dendrotweaks.biophys.io.converter import MODFileConverter
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            from dendrotweaks.biophys.io.reader import MODFileReader
         | 
| 5 | 
            +
            from dendrotweaks.biophys.io.parser import MODFileParser
         | 
| 6 | 
            +
            from dendrotweaks.biophys.io.code_generators import PythonCodeGenerator
         | 
| 7 | 
            +
            from dendrotweaks.biophys.io.code_generators import NMODLCodeGenerator
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            from dendrotweaks.biophys.io.factories import create_channel
         | 
| 10 | 
            +
            from dendrotweaks.biophys.io.factories import create_standard_channel
         | 
| 11 | 
            +
            from dendrotweaks.biophys.io.factories import standardize_channel
         | 
| @@ -90,6 +90,12 @@ class AbstracSyntaxTree(): | |
| 90 90 | 
             
                    return {k:v for k, v in self.params.items()
         | 
| 91 91 | 
             
                            if k in self['NEURON']['range']}
         | 
| 92 92 |  | 
| 93 | 
            +
                @property
         | 
| 94 | 
            +
                def current_available(self):
         | 
| 95 | 
            +
                    """
         | 
| 96 | 
            +
                    Returns True if the current is available in the mechanism.
         | 
| 97 | 
            +
                    """
         | 
| 98 | 
            +
                    return 'i' in self['NEURON']['range']
         | 
| 93 99 |  | 
| 94 100 | 
             
                # ASSIGNED block
         | 
| 95 101 | 
             
                @property
         | 
| @@ -72,6 +72,7 @@ class PythonCodeGenerator(CodeGenerator): | |
| 72 72 | 
             
                        'independent_var_name': ast.independent_var_name,
         | 
| 73 73 | 
             
                        'channel_params': ast.params,
         | 
| 74 74 | 
             
                        'range_params': ast.range_params,
         | 
| 75 | 
            +
                        'current_available': ast.current_available,
         | 
| 75 76 | 
             
                        'state_vars': ast.state_vars,
         | 
| 76 77 | 
             
                        'functions': self._generate_functions(ast),
         | 
| 77 78 | 
             
                        'procedures': self._generate_procedures(ast),
         | 
| @@ -296,10 +297,15 @@ class NMODLCodeGenerator(CodeGenerator): | |
| 296 297 | 
             
                    variables = {
         | 
| 297 298 | 
             
                        'suffix': channel.name,
         | 
| 298 299 | 
             
                        'ion': channel.ion,
         | 
| 299 | 
            -
                        ' | 
| 300 | 
            +
                        'params': [
         | 
| 300 301 | 
             
                            (param, channel.params[param], get_unit(param))
         | 
| 301 302 | 
             
                            for param in channel.params
         | 
| 302 303 | 
             
                        ],
         | 
| 304 | 
            +
                        'range_params': [
         | 
| 305 | 
            +
                            (param, channel.range_params[param], get_unit(param))
         | 
| 306 | 
            +
                            for param in channel.range_params
         | 
| 307 | 
            +
                        ],
         | 
| 308 | 
            +
                        'has_tadj': ('q10' in channel.params and 'temp' in channel.params),
         | 
| 303 309 | 
             
                        'state_vars': {
         | 
| 304 310 | 
             
                            var: params['power'] for var, params in channel._state_powers.items()
         | 
| 305 311 | 
             
                        },
         | 
| @@ -1,6 +1,6 @@ | |
| 1 | 
            -
            from dendrotweaks. | 
| 2 | 
            -
            from dendrotweaks. | 
| 3 | 
            -
            from dendrotweaks. | 
| 1 | 
            +
            from dendrotweaks.biophys.io.reader import MODFileReader
         | 
| 2 | 
            +
            from dendrotweaks.biophys.io.parser import MODFileParser
         | 
| 3 | 
            +
            from dendrotweaks.biophys.io.code_generators import PythonCodeGenerator
         | 
| 4 4 |  | 
| 5 5 | 
             
            class MODFileConverter():
         | 
| 6 6 | 
             
                """
         | 
| @@ -3,9 +3,9 @@ import sys | |
| 3 3 | 
             
            from typing import List, Tuple
         | 
| 4 4 |  | 
| 5 5 |  | 
| 6 | 
            -
            from dendrotweaks. | 
| 7 | 
            -
            from dendrotweaks. | 
| 8 | 
            -
            from dendrotweaks. | 
| 6 | 
            +
            from dendrotweaks.biophys.io.converter import MODFileConverter
         | 
| 7 | 
            +
            from dendrotweaks.biophys.io.code_generators import NMODLCodeGenerator
         | 
| 8 | 
            +
            from dendrotweaks.biophys.mechanisms import Mechanism, IonChannel, StandardIonChannel
         | 
| 9 9 |  | 
| 10 10 |  | 
| 11 11 | 
             
            def create_channel(path_to_mod_file: str,
         | 
| @@ -86,10 +86,12 @@ def standardize_channel(channel: IonChannel, | |
| 86 86 | 
             
                                                      state_powers=channel._state_powers, 
         | 
| 87 87 | 
             
                                                      ion=channel.ion)
         | 
| 88 88 |  | 
| 89 | 
            -
                 | 
| 90 | 
            -
             | 
| 89 | 
            +
                if 'q10' in channel.params:
         | 
| 90 | 
            +
                    standard_channel.params['q10'] = channel.params['q10']
         | 
| 91 | 
            +
                if 'temp' in channel.params:
         | 
| 92 | 
            +
                    standard_channel.params['temp'] = channel.params['temp']
         | 
| 91 93 |  | 
| 92 | 
            -
                fit_temperature = channel.params.get('temp') | 
| 94 | 
            +
                fit_temperature = channel.params.get('temp')
         | 
| 93 95 |  | 
| 94 96 | 
             
                standard_channel.set_tadj(fit_temperature)
         | 
| 95 97 | 
             
                # Fit the standard channel to the data
         | 
| @@ -0,0 +1,190 @@ | |
| 1 | 
            +
            import os
         | 
| 2 | 
            +
            import sys
         | 
| 3 | 
            +
            import shutil
         | 
| 4 | 
            +
            import subprocess
         | 
| 5 | 
            +
            import neuron
         | 
| 6 | 
            +
            from neuron import h
         | 
| 7 | 
            +
             | 
| 8 | 
            +
             | 
| 9 | 
            +
            class MODFileLoader():
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def __init__(self):
         | 
| 12 | 
            +
                    self._loaded_mechanisms = set()
         | 
| 13 | 
            +
                    self.verbose = False
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def _log(self, message):
         | 
| 16 | 
            +
                    """Print a message if verbose mode is enabled."""
         | 
| 17 | 
            +
                    if self.verbose:
         | 
| 18 | 
            +
                        print(message)
         | 
| 19 | 
            +
                          
         | 
| 20 | 
            +
                # LOADING METHODS
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def _get_mechanism_dir(self, path_to_mod_file: str) -> str:
         | 
| 23 | 
            +
                    """
         | 
| 24 | 
            +
                    Get the subdirectory for the given mod file.
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    Parameters
         | 
| 27 | 
            +
                    ----------
         | 
| 28 | 
            +
                    path_to_mod_file : str
         | 
| 29 | 
            +
                        Path to the .mod file.
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    Returns
         | 
| 32 | 
            +
                    -------
         | 
| 33 | 
            +
                    str
         | 
| 34 | 
            +
                        Path to the subdirectory for the mechanism.
         | 
| 35 | 
            +
                    """
         | 
| 36 | 
            +
                    mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
         | 
| 37 | 
            +
                    parent_dir = os.path.dirname(path_to_mod_file)
         | 
| 38 | 
            +
                    if sys.platform.startswith('win'):
         | 
| 39 | 
            +
                        return os.path.join(parent_dir, mechanism_name, mechanism_name)
         | 
| 40 | 
            +
                    else:
         | 
| 41 | 
            +
                        return os.path.join(parent_dir, mechanism_name)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def _clean_mechanism_dir(self, mechanism_dir: str) -> None:
         | 
| 44 | 
            +
                    
         | 
| 45 | 
            +
                    if sys.platform.startswith('win'):
         | 
| 46 | 
            +
                        parent_dir = os.path.dirname(mechanism_dir)
         | 
| 47 | 
            +
                        shutil.rmtree(parent_dir)
         | 
| 48 | 
            +
                    else:
         | 
| 49 | 
            +
                        shutil.rmtree(mechanism_dir)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
             | 
| 52 | 
            +
                def load_mechanism(self, path_to_mod_file: str, 
         | 
| 53 | 
            +
                                   recompile: bool = False) -> None:
         | 
| 54 | 
            +
                    """
         | 
| 55 | 
            +
                    Load a mechanism from the specified mod file.
         | 
| 56 | 
            +
                    Uses the NEURON neuron.load_mechanisms method to make
         | 
| 57 | 
            +
                    the mechanism available in the hoc interpreter.
         | 
| 58 | 
            +
                    Creates a temporary directory for the mechanism files
         | 
| 59 | 
            +
                    to be able to dynamically load mechanisms.
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    Parameters
         | 
| 62 | 
            +
                    ----------
         | 
| 63 | 
            +
                    path_to_mod_file : str
         | 
| 64 | 
            +
                        Path to the .mod file.
         | 
| 65 | 
            +
                    recompile : bool
         | 
| 66 | 
            +
                        Force recompilation even if already compiled.
         | 
| 67 | 
            +
                    """
         | 
| 68 | 
            +
                    mechanism_name = os.path.basename(path_to_mod_file).replace('.mod', '')
         | 
| 69 | 
            +
                    mechanism_dir = self._get_mechanism_dir(path_to_mod_file)      
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    if self.verbose: print(f"{'=' * 60}\nLoading mechanism {mechanism_name} to NEURON...")
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    # Check if the mechanism is already loaded
         | 
| 74 | 
            +
                    if mechanism_name in self._loaded_mechanisms:
         | 
| 75 | 
            +
                        self._log(f'Mechanism "{mechanism_name}" already loaded')
         | 
| 76 | 
            +
                        return
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    if recompile and os.path.exists(mechanism_dir):
         | 
| 79 | 
            +
                        self._clean_mechanism_dir(mechanism_dir)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    self._separate_and_compile(mechanism_name, mechanism_dir, path_to_mod_file)
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                    # Load the mechanism
         | 
| 84 | 
            +
                    self._load_mechanism(mechanism_name, mechanism_dir)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
             | 
| 87 | 
            +
                # HELPER METHODS
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def _separate_and_compile(self, mechanism_name, mechanism_dir, path_to_mod_file):
         | 
| 90 | 
            +
                    """
         | 
| 91 | 
            +
                    Separate the mechanism files into their own directory and compile them.
         | 
| 92 | 
            +
                    Separation is done to enable dynamic loading of mechanisms.
         | 
| 93 | 
            +
                    Compilation is done using the appropriate command based on the platform.
         | 
| 94 | 
            +
                    
         | 
| 95 | 
            +
                    Parameters
         | 
| 96 | 
            +
                    ----------
         | 
| 97 | 
            +
                    mechanism_name : str
         | 
| 98 | 
            +
                        Name of the mechanism.
         | 
| 99 | 
            +
                    mechanism_dir : str
         | 
| 100 | 
            +
                        Directory to store the mechanism files.
         | 
| 101 | 
            +
                    path_to_mod_file : str
         | 
| 102 | 
            +
                        Path to the .mod file.
         | 
| 103 | 
            +
                    """
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    if sys.platform.startswith('win'):
         | 
| 106 | 
            +
                        dll_file = os.path.join(os.path.dirname(mechanism_dir), 'nrnmech.dll')
         | 
| 107 | 
            +
                        if not os.path.exists(dll_file):
         | 
| 108 | 
            +
                            self._log(f'Compiling mechanism "{mechanism_name}"...')
         | 
| 109 | 
            +
                            os.makedirs(mechanism_dir, exist_ok=True)
         | 
| 110 | 
            +
                            shutil.copy(path_to_mod_file, mechanism_dir)
         | 
| 111 | 
            +
                            self._compile_files(mechanism_dir, ["mknrndll"], shell=True)
         | 
| 112 | 
            +
                    else:
         | 
| 113 | 
            +
                        x86_64_dir = os.path.join(mechanism_dir, 'x86_64')
         | 
| 114 | 
            +
                        if not os.path.exists(x86_64_dir):
         | 
| 115 | 
            +
                            self._log(f'Compiling mechanism "{mechanism_name}"...')
         | 
| 116 | 
            +
                            os.makedirs(mechanism_dir, exist_ok=True)
         | 
| 117 | 
            +
                            shutil.copy(path_to_mod_file, mechanism_dir)
         | 
| 118 | 
            +
                            self._compile_files(mechanism_dir, ["nrnivmodl"])
         | 
| 119 | 
            +
             | 
| 120 | 
            +
             | 
| 121 | 
            +
                def _load_mechanism(self, mechanism_name: str, mechanism_dir: str) -> None:
         | 
| 122 | 
            +
                    """
         | 
| 123 | 
            +
                    Load the mechanism into NEURON using neuron.load_mechanisms.
         | 
| 124 | 
            +
                    This method checks if the mechanism is already loaded
         | 
| 125 | 
            +
                    and only loads it if not.
         | 
| 126 | 
            +
                    Parameters
         | 
| 127 | 
            +
                    ----------
         | 
| 128 | 
            +
                    mechanism_name : str
         | 
| 129 | 
            +
                        Name of the mechanism.
         | 
| 130 | 
            +
                    mechanism_dir : str
         | 
| 131 | 
            +
                        Directory containing the compiled mechanism files.
         | 
| 132 | 
            +
                    """
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    if hasattr(h, mechanism_name):
         | 
| 135 | 
            +
                        self._log(f'Mechanism "{mechanism_name}" already exists in hoc')
         | 
| 136 | 
            +
                    else:
         | 
| 137 | 
            +
                        try:
         | 
| 138 | 
            +
                            neuron.load_mechanisms(mechanism_dir)
         | 
| 139 | 
            +
                        except Exception as e:
         | 
| 140 | 
            +
                            print(f"Failed to load mechanism {mechanism_name}: {e}")
         | 
| 141 | 
            +
                            return
         | 
| 142 | 
            +
                    self._loaded_mechanisms.add(mechanism_name)
         | 
| 143 | 
            +
                    self._log(f'Loaded mechanism "{mechanism_name}"')
         | 
| 144 | 
            +
                    
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                
         | 
| 147 | 
            +
                def _compile_files(self, path, command, shell=False):
         | 
| 148 | 
            +
                    """
         | 
| 149 | 
            +
                    Compile the MOD files in the specified directory.
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    Parameters
         | 
| 152 | 
            +
                    ----------
         | 
| 153 | 
            +
                    path : str or Path
         | 
| 154 | 
            +
                        Directory containing MOD files to compile.
         | 
| 155 | 
            +
                    command : list
         | 
| 156 | 
            +
                        Compilation command to execute. Either "mknrndll" or "nrnivmodl".
         | 
| 157 | 
            +
                    shell : bool
         | 
| 158 | 
            +
                        Whether to use shell=True for subprocess.run
         | 
| 159 | 
            +
                        (Windows compatibility).
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    Returns
         | 
| 162 | 
            +
                    -------
         | 
| 163 | 
            +
                    bool
         | 
| 164 | 
            +
                        True if compilation succeeded, False otherwise.
         | 
| 165 | 
            +
                    """
         | 
| 166 | 
            +
                    path_str = str(path)
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                    try:
         | 
| 169 | 
            +
                        result = subprocess.run(
         | 
| 170 | 
            +
                            command,
         | 
| 171 | 
            +
                            cwd=path_str,
         | 
| 172 | 
            +
                            check=True,
         | 
| 173 | 
            +
                            capture_output=True,
         | 
| 174 | 
            +
                            text=True,
         | 
| 175 | 
            +
                            shell=shell
         | 
| 176 | 
            +
                        )
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                        self._log("Compilation successful.")
         | 
| 179 | 
            +
                        if self.verbose and result.stdout:
         | 
| 180 | 
            +
                            print(result.stdout)
         | 
| 181 | 
            +
                        return
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                    except subprocess.CalledProcessError as e:
         | 
| 184 | 
            +
                        print(f"Compilation failed with return code {e.returncode}")
         | 
| 185 | 
            +
                        if self.verbose:
         | 
| 186 | 
            +
                            if e.stdout:
         | 
| 187 | 
            +
                                print("Compiler output:\n", e.stdout)
         | 
| 188 | 
            +
                            if e.stderr:
         | 
| 189 | 
            +
                                print("Compiler errors:\n", e.stderr)
         | 
| 190 | 
            +
                        return
         | 
| @@ -2,14 +2,14 @@ import re | |
| 2 2 | 
             
            import pprint
         | 
| 3 3 | 
             
            from typing import List, Dict, Union, Any
         | 
| 4 4 |  | 
| 5 | 
            -
            from dendrotweaks. | 
| 6 | 
            -
            from dendrotweaks. | 
| 7 | 
            -
            from dendrotweaks. | 
| 8 | 
            -
            from dendrotweaks. | 
| 9 | 
            -
            from dendrotweaks. | 
| 10 | 
            -
            from dendrotweaks. | 
| 11 | 
            -
             | 
| 12 | 
            -
            from dendrotweaks. | 
| 5 | 
            +
            from dendrotweaks.biophys.io.grammar import title, comment_block
         | 
| 6 | 
            +
            from dendrotweaks.biophys.io.grammar import neuron_block
         | 
| 7 | 
            +
            from dendrotweaks.biophys.io.grammar import units_block, parameter_block, assigned_block
         | 
| 8 | 
            +
            from dendrotweaks.biophys.io.grammar import state_block
         | 
| 9 | 
            +
            from dendrotweaks.biophys.io.grammar import breakpoint_block, derivative_block, initial_block
         | 
| 10 | 
            +
            from dendrotweaks.biophys.io.grammar import function_block, procedure_block
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            from dendrotweaks.biophys.io.ast import AbstracSyntaxTree
         | 
| 13 13 |  | 
| 14 14 |  | 
| 15 15 | 
             
            class MODFileParser():
         | 
| @@ -32,6 +32,7 @@ class Mechanism(): | |
| 32 32 | 
             
                    self.name = name
         | 
| 33 33 | 
             
                    self.params = {}
         | 
| 34 34 | 
             
                    self.range_params = {}
         | 
| 35 | 
            +
                    self.current_available = False
         | 
| 35 36 |  | 
| 36 37 | 
             
                @property
         | 
| 37 38 | 
             
                def params_with_suffix(self):
         | 
| @@ -119,9 +120,12 @@ class IonChannel(Mechanism): | |
| 119 120 | 
             
                    where q10 is the temperature coefficient and reference_temp is the
         | 
| 120 121 | 
             
                    temperature at which the channel kinetics were measured.
         | 
| 121 122 | 
             
                    """
         | 
| 122 | 
            -
                    q10 = self.params.get("q10" | 
| 123 | 
            -
                    reference_temp = self.params.get("temp" | 
| 124 | 
            -
                     | 
| 123 | 
            +
                    q10 = self.params.get("q10")
         | 
| 124 | 
            +
                    reference_temp = self.params.get("temp")
         | 
| 125 | 
            +
                    if q10 is None or reference_temp is None:
         | 
| 126 | 
            +
                        self.tadj = 1
         | 
| 127 | 
            +
                    else:
         | 
| 128 | 
            +
                        self.tadj = q10 ** ((temperature - reference_temp) / 10)
         | 
| 125 129 |  | 
| 126 130 | 
             
                def get_data(self, x=None, temperature: float = 37, verbose=True) -> Dict[str, Dict[str, float]]:
         | 
| 127 131 | 
             
                    """
         | 
| @@ -367,14 +371,14 @@ class StandardIonChannel(IonChannel): | |
| 367 371 | 
             
                    #             for param in self.STANDARD_PARAMS]
         | 
| 368 372 |  | 
| 369 373 | 
             
                    self.params = {
         | 
| 374 | 
            +
                        'gbar': 0.0,
         | 
| 375 | 
            +
                        **{
         | 
| 370 376 | 
             
                        f'{param}_{state}': None
         | 
| 371 377 | 
             
                        for state in state_powers
         | 
| 372 378 | 
             
                        for param in self.STANDARD_PARAMS
         | 
| 379 | 
            +
                        }
         | 
| 373 380 | 
             
                    }
         | 
| 374 | 
            -
                    self.params. | 
| 375 | 
            -
                        'gbar': 0.0,
         | 
| 376 | 
            -
                    })
         | 
| 377 | 
            -
                    self.range_params = self.params.copy()
         | 
| 381 | 
            +
                    self.range_params = {k:v for k, v in self.params.items()}
         | 
| 378 382 |  | 
| 379 383 | 
             
                    self.temperature = 37
         | 
| 380 384 |  | 
| @@ -474,9 +478,9 @@ class StandardIonChannel(IonChannel): | |
| 474 478 | 
             
                            fit_result.params = {key: round(value, round_params) for key, value in fit_result.params.items()}
         | 
| 475 479 |  | 
| 476 480 | 
             
                        for param in ['k', 'delta', 'tau0', 'vhalf', 'sigma']:
         | 
| 477 | 
            -
                             | 
| 478 | 
            -
             | 
| 479 | 
            -
             | 
| 481 | 
            +
                            value = fit_result.params[param]
         | 
| 482 | 
            +
                            self.params[f'{param}_{state}'] = value
         | 
| 483 | 
            +
                            self.range_params[f'{param}_{state}'] = value
         | 
| 480 484 |  | 
| 481 485 |  | 
| 482 486 | 
             
                def to_dict(self):
         |