Source code for myogen.utils.nmodl

"""
Initialize and set up NMODL (NEURON MODeling Language) files for the model.

This module handles the compilation and loading of NMODL files, which are used to define
custom mechanisms and models in NEURON simulations. It performs the following steps:
1. Locates and copies NMODL files to the appropriate directory
2. Compiles the NMODL files (platform-specific approach)
3. Loads the compiled files into NEURON

The module is automatically executed when the package is imported.
"""

import os
import platform
import shutil
import subprocess
from pathlib import Path
from typing import List, Optional

from neuron import h


def find_nmodl_directory() -> Path:
    """Find the NMODL directory in the pyNN/neuron installation."""
    src_path = Path(__file__).parent.parent / "simulator"
    try:
        nmodl_path = next(src_path.parent.parent.rglob("*pyNN/neuron/nmodl"))
        return nmodl_path
    except StopIteration:
        print("Error: Could not find pyNN/neuron/nmodl directory")
        raise FileNotFoundError("Could not find pyNN/neuron/nmodl directory")


def _copy_mod_files(src_path: Path, nmodl_path: Path) -> List[Path]:
    """Copy .mod files from source to NMODL directory."""
    mod_files = list(src_path.glob("*mod"))
    if not mod_files:
        print("Warning: No .mod files found in source directory")
        return []

    for mod_file in mod_files:
        try:
            shutil.copy(mod_file, nmodl_path / mod_file.name)
            print(f"Copied {mod_file.stem}")
        except (shutil.Error, IOError) as e:
            print(f"Error: Failed to copy {mod_file.stem}: {str(e)}")
            raise

    return mod_files


def _find_mknrndll() -> Optional[Path]:
    """Find the mknrndll executable on Windows systems."""
    # Common locations for mknrndll
    possible_locations = [
        Path(os.environ.get("NEURONHOME", "")) / "bin",
        Path(os.environ.get("NEURONHOME", "")) / "mingw",
        Path("C:/nrn/bin"),
        Path("C:/Program Files/NEURON/bin"),
        Path("C:/Program Files (x86)/NEURON/bin"),
    ]

    print("Searching for mknrndll.bat in common locations...")
    for location in possible_locations:
        if location and location.parent.exists():  # Check if parent directory exists
            mknrndll_path = location / "mknrndll.bat"
            print(f"  Checking: {mknrndll_path}")
            if mknrndll_path.exists():
                print(f"  ✓ Found: {mknrndll_path}")
                return mknrndll_path
            else:
                print(f"  ✗ Not found")

    # Try to find it in PATH
    print("Searching for mknrndll.bat in PATH...")
    try:
        result = subprocess.run(
            ["where", "mknrndll.bat"], capture_output=True, text=True, check=False
        )
        if result.returncode == 0:
            found_path = Path(result.stdout.strip())
            print(f"  ✓ Found in PATH: {found_path}")
            return found_path
        else:
            print("  ✗ Not found in PATH")
    except Exception as e:
        print(f"  ✗ Error searching PATH: {e}")

    print("mknrndll.bat not found. Please ensure NEURON is properly installed.")
    return None


def _compile_mod_files_windows(nmodl_path: Path) -> None:
    """Compile NMODL files on Windows using mknrndll."""
    mknrndll_path = _find_mknrndll()

    if mknrndll_path is None:
        raise FileNotFoundError(
            "Could not find mknrndll.bat. Please make sure NEURON is properly installed "
            "and NEURONHOME environment variable is set correctly."
        )

    print(f"Using mknrndll: {mknrndll_path}")

    # Change to the directory containing the mod files and run mknrndll.bat
    original_dir = os.getcwd()
    try:
        os.chdir(nmodl_path)

        # Remove any existing DLL files to avoid conflicts
        for dll_file in nmodl_path.glob("*nrnmech.dll"):
            try:
                dll_file.unlink()
                print(f"Removed existing DLL: {dll_file.name}")
            except Exception as e:
                print(f"Warning: Could not remove {dll_file.name}: {e}")

        # On Windows, we need to use cmd.exe to run batch files
        cmd = ["cmd", "/c", str(mknrndll_path)]
        print(f"Running command: {' '.join(cmd)}")
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        print(result.stdout)

        # Check if stderr has any warnings (not necessarily errors)
        if result.stderr:
            print(f"Compilation warnings/info: {result.stderr}")

    except subprocess.CalledProcessError as e:
        print(f"Error during compilation: {e.stderr}")
        print(f"Stdout: {e.stdout}")
        raise
    finally:
        os.chdir(original_dir)


def _compile_mod_files_unix(nmodl_path: Path) -> None:
    """Compile NMODL files on Unix-like systems using pyNN's utility."""
    from pyNN.utility.build import compile_nmodl

    try:
        print(f"Compiling NMODL files from {nmodl_path}")
        compile_nmodl(nmodl_path)
    except Exception as e:
        print(f"Error: Failed to compile NMODL files: {str(e)}")
        raise


def _compile_and_load_mod_files(nmodl_path: Path, mod_files: List[Path]) -> None:
    """Compile and load NMODL files into NEURON based on platform."""
    if not mod_files:
        print("No mod files to compile")
        return

    # Platform-specific compilation
    if platform.system() == "Windows":
        _compile_mod_files_windows(nmodl_path)
    else:
        _compile_mod_files_unix(nmodl_path)

    # Load the compiled mechanisms
    # The location and naming of compiled files differs between platforms
    if platform.system() == "Windows":
        # On Windows, NEURON creates a DLL file, but the name might have a prefix
        # Look for both 'nrnmech.dll' and '*.nrnmech.dll' patterns
        dll_files = list(nmodl_path.glob("*nrnmech.dll"))

        if dll_files:
            # Use the first DLL found (usually there should be only one)
            dll_path = dll_files[0]
            try:
                h.nrn_load_dll(str(dll_path))
                print(f"Successfully loaded {dll_path.name}")
            except Exception as e:
                print(f"Warning: Error loading {dll_path.name}: {str(e)}")
                print(
                    "This may be because some mechanisms are already loaded, which is usually not a problem."
                )
                # Continue execution - don't re-raise the exception
        else:
            print(
                f"Warning: No nrnmech.dll file was found after compilation in {nmodl_path}"
            )
            print("Available files:")
            for item in nmodl_path.iterdir():
                if item.is_file():
                    print(f"  {item.name}")
    else:
        # On Unix, load individual .o files
        for mod_file in mod_files:
            o_file_path = str(nmodl_path / f"{mod_file.stem}.o")
            try:
                print(f"Loading {o_file_path}")
                h.nrn_load_dll(o_file_path)
                print(f"Successfully loaded {mod_file.stem}")
            except Exception as e:
                print(f"Warning: Failed to load {mod_file.stem}: {str(e)}")
                print(
                    "This may be because the mechanism is already loaded, which is usually not a problem."
                )


[docs] def load_nmodl_files(force_reload: bool = False, quiet: bool = False): """ Main function to handle NMODL file setup. Args: force_reload: If True, force recompilation even if mechanisms seem loaded quiet: If True, suppress most output messages """ def log(message: str): if not quiet: print(message) # Check if mechanisms are already loaded by trying to import neuron and check for our mechanisms if not force_reload: try: from neuron import h # Try to access one of our custom mechanisms to see if it's already loaded h.AdExpIF # This will fail if mechanisms aren't loaded, which is what we want log("NMODL mechanisms appear to already be loaded, skipping reload") return True except (AttributeError, NameError): # Mechanisms not loaded, proceed with loading pass except ImportError: log("Warning: NEURON not available, skipping NMODL loading") return False except Exception as e: log(f"Warning: Error checking mechanism status: {e}") # Continue with loading attempt try: src_path = Path(__file__).parent.parent / "simulator" log(f"Loading NMODL files from {src_path}") nmodl_path = find_nmodl_directory() mod_files = _copy_mod_files(src_path / "nmodl_files", nmodl_path) if mod_files: _compile_and_load_mod_files(nmodl_path, mod_files) log("NMODL files processing complete!") return True else: log("Warning: No NMODL files were processed") return False except Exception as e: log(f"Error during NMODL setup: {str(e)}") if not quiet: # Log the error but don't crash the program import traceback traceback.print_exc() return False
load_nmodl_files()