Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions brian2cuda/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@
Package implementing the CUDA "standalone" `Device` and `CodeObject`.
"""
import logging
import os

from . import cuda_prefs
from . import binomial, cuda_prefs, timedarray
from .codeobject import CUDAStandaloneCodeObject
from .device import cuda_standalone_device
from . import binomial
from . import timedarray


try:
from ._version import __version__, __version_tuple__
Expand Down Expand Up @@ -36,6 +34,49 @@
from .tests import run as test


def _load_preference_files():
"""
Load brian2cuda preference files from standard locations.

This function loads brian2cuda-specific preferences from user preference
files, avoiding the validation error that occurs when external package
preferences are added to the main preference file.

Files are loaded in the following order (later files override earlier ones):
1. ~/.brian2cuda_preferences (user-specific preferences)
2. ./brian2cuda_preferences (project-specific preferences)

Missing files are silently ignored. Invalid files generate warnings but
do not prevent the package from loading.
"""
from brian2 import prefs

# Define preference file locations
user_prefs_file = os.path.join(
os.path.expanduser('~'), '.brian2cuda_preferences'
)
local_prefs_file = 'brian2cuda_preferences'

preference_files = [user_prefs_file, local_prefs_file]

for prefs_file in preference_files:
try:
prefs.read_preference_file(prefs_file)
except OSError:
# File doesn't exist, that's fine
pass
except Exception as e:
# Log a warning for other errors (invalid format, etc.)
logger = logging.getLogger('brian2cuda')
logger.warning(
f"Error reading preference file '{prefs_file}': {e}"
)


# Load preference files when the package is imported
_load_preference_files()


def example_run(device_name="cuda_standalone", directory=None, **build_options):
"""
Run a simple example simulation to test whether Brian2CUDA is correctly set up.
Expand All @@ -51,12 +92,13 @@ def example_run(device_name="cuda_standalone", directory=None, **build_options):
build_options : dict, optional
Additional options that will be forwarded to the ``device.build`` call,
"""
from brian2.devices.device import device, set_device
from brian2 import ms, NeuronGroup, run
import brian2cuda
import numpy as np
from numpy.testing import assert_allclose

import brian2cuda
from brian2 import NeuronGroup, ms, run
from brian2.devices.device import device, set_device

set_device(device_name, build_on_run=False)
N = 100
tau = 10 * ms
Expand Down
122 changes: 116 additions & 6 deletions brian2cuda/tests/test_gpu_detection.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import functools
import os
import logging
import os
from io import StringIO

import pytest
from numpy.testing import assert_equal

from brian2 import prefs, ms, run, set_device, device
from brian2.utils.logger import catch_logs as _catch_logs
from brian2 import device, ms, prefs, run, set_device
from brian2.core.preferences import PreferenceError
from brian2.utils.logger import catch_logs as _catch_logs
from brian2cuda.utils.gputools import (
reset_cuda_installation,
get_cuda_installation,
restore_cuda_installation,
reset_gpu_selection,
get_gpu_selection,
reset_cuda_installation,
reset_gpu_selection,
restore_cuda_installation,
restore_gpu_selection,
)

Expand Down Expand Up @@ -177,3 +177,113 @@ def test_no_gpu_detection_preference(reset_gpu_detection, use_default_prefs):
prefs.devices.cuda_standalone.cuda_backend.gpu_id = 0
prefs.devices.cuda_standalone.cuda_backend.compute_capability = device.minimal_compute_capability
run(0*ms)


### Preference file loading tests (Issue #281) ###
@pytest.mark.codegen_independent
def test_missing_files_no_error(use_default_prefs):
"""
Test that missing preference files don't cause errors.

Regression test for issue #281:
https://github.com/brian-team/brian2cuda/issues/281
"""
import importlib
import tempfile

import brian2cuda

with tempfile.TemporaryDirectory() as tmpdir:
original_home = os.environ.get('HOME')
original_cwd = os.getcwd()

try:
# Use empty directory (no preference files)
os.environ['HOME'] = tmpdir
os.chdir(tmpdir)

# Should reload without error
importlib.reload(brian2cuda)

# Check it doesn't crash - preference should have a value
assert prefs['devices.cuda_standalone.SM_multiplier'] is not None
finally:
if original_home:
os.environ['HOME'] = original_home
os.chdir(original_cwd)


@pytest.mark.codegen_independent
def test_load_user_preference_file(tmp_path, use_default_prefs):
"""
Test that user preference file (~/.brian2cuda_preferences) loads.

Regression test for issue #281:
https://github.com/brian-team/brian2cuda/issues/281
"""
import importlib

import brian2cuda

# Create preference file in user's home directory
prefs_file = tmp_path / ".brian2cuda_preferences"
prefs_file.write_text("""
[devices.cuda_standalone]
SM_multiplier = 3
launch_bounds = True
""")

original_home = os.environ.get('HOME')
os.environ['HOME'] = str(tmp_path)

try:
# Reload to trigger preference loading
importlib.reload(brian2cuda)

# Check preferences were loaded
assert prefs['devices.cuda_standalone.SM_multiplier'] == 3
assert prefs['devices.cuda_standalone.launch_bounds'] is True
finally:
if original_home:
os.environ['HOME'] = original_home


@pytest.mark.codegen_independent
def test_local_overrides_user(tmp_path, monkeypatch, use_default_prefs):
"""
Test that local preference file overrides user file.

Regression test for issue #281:
https://github.com/brian-team/brian2cuda/issues/281
"""
import importlib

import brian2cuda

# Create user file
user_file = tmp_path / ".brian2cuda_preferences"
user_file.write_text("""
[devices.cuda_standalone]
SM_multiplier = 2
""")

# Create local file with different value
local_file = tmp_path / "brian2cuda_preferences"
local_file.write_text("""
[devices.cuda_standalone]
SM_multiplier = 5
""")

original_home = os.environ.get('HOME')
os.environ['HOME'] = str(tmp_path)
monkeypatch.chdir(tmp_path)

try:
# Reload to trigger preference loading
importlib.reload(brian2cuda)

# Local should override user
assert prefs['devices.cuda_standalone.SM_multiplier'] == 5
finally:
if original_home:
os.environ['HOME'] = original_home
Loading