NNGT/nngt/__init__.py

374 lines
9.4 KiB
Python

# -*- coding: utf-8 -*-
# SPDX-FileCopyrightText: 2015-2023 Tanguy Fardet
# SPDX-License-Identifier: GPL-3.0-or-later
# nngt/__init__.py
"""
NNGT
====
Package aimed at facilitating the analysis of Neural Networks and Graphs'
Topologies in Python by providing a unified interface for network generation
and analysis.
The library mainly provides algorithms for
1. generating networks
2. studying their topological properties
3. doing some basic spatial, topological, and statistical visualizations
4. interacting with neuronal simulators and analyzing neuronal activity
Available modules
-----------------
analysis
Tools to study graph topology and neuronal activity.
core
Where the main classes are coded; however, most useful classes and methods
for users are loaded at the main level (`nngt`) when the library is imported,
so `nngt.core` should generally not be used.
generation
Functions to generate specific networks.
geometry
Tools to work on metric graphs (see
`PyNCulture <https://github.com/SENeC-Initiative/PyNCulture>`_).
io
Tools for input/output operations.
lib
Basic functions used by several most other modules.
simulation
Tools to provide complex network generation with NEST and help analyze the
influence of the network structure on neuronal activity.
plot
Plot data or graphs using matplotlib.
Units
-----
Functions related to spatial embedding of networks are using micrometers
(um) as default unit; other units from the metric system can also be
provided:
- `mm` for milimeters
- `cm` centimeters
- `dm` for decimeters
- `m` for meters
Main classes and functions
==========================
"""
import os as _os
import errno as _errno
import importlib.util as _imputil
import shutil as _shutil
import sys as _sys
import logging as _logging
import numpy as _np
__version__ = '2.7.0.dev'
# ----------------------- #
# Requirements and config #
# ----------------------- #
# IMPORTANT: configuration MUST come first
_config = {
'color_lib': 'matplotlib',
'db_folder': "~/.nngt/database",
'db_name': "main",
'db_to_file': False,
'db_url': None,
'graph': object,
'backend': "nngt",
'library': None,
'log_folder': "~/.nngt/log",
'log_level': 10,
'log_to_file': False,
'mpi': False,
'mpi_comm': None,
'mpl_backend': None,
'msd': None,
'multithreading': True,
'omp': 1,
'palette_continuous': 'magma',
'palette_discrete': 'Set1',
'use_database': False,
'use_tex': False,
'seeds': None,
'load_nest': True,
'load_gis': True,
'with_nest': False,
'with_plot': False,
}
# tools for nest interactions (can be used in config)
_old_nest_func = {}
# random generator for numpy
_rng = _np.random.default_rng()
# state of master seed (already seeded or not)
_seeded = False
# state of local seeds for multithreading or MPI (already used or not)
_seeded_local = False
_used_local = False
# database (predeclare here, can be used in config)
_db = None
_main_db = None
# configuration folders and files
_lib_folder = _os.path.expanduser('~') + '/.nngt'
_new_config = _os.path.expanduser('~') + '/.nngt/nngt.conf'
_default_config = _os.path.dirname(_os.path.realpath(__file__)) + \
'/nngt.conf.default'
# check that library config folder exists
if not _os.path.isdir(_lib_folder):
try:
_os.mkdir(_lib_folder)
except OSError as e:
if e.errno != _errno.EEXIST:
raise
# IMPORTANT: first create logger
from .lib.logger import _init_logger, _log_message
_logger = _logging.getLogger(__name__)
_init_logger(_logger)
# IMPORTANT: afterwards, import config
from .lib.nngt_config import (get_config, set_config, _load_config, _convert,
_log_conf_changed, _lazy_load, _config_info)
# check that config file exists
if not _os.path.isfile(_new_config): # if it does not, create it
_shutil.copy(_default_config, _new_config)
else: # if it does check it is up-to-date
with open(_new_config, 'r+') as fconfig:
_options = [l.strip() for l in fconfig if l.strip() and l[0] != "#"]
config_version = ""
for _opt in _options:
sep = _opt.find("=")
_opt_name = _opt[:sep].strip()
_opt_val = _convert(_opt[sep+1:].strip())
if _opt_name == "version":
config_version = _opt_val
if config_version != __version__:
fconfig.seek(0)
data = []
with open(_default_config) as fdefault:
data = [l for l in fdefault]
i = 0
for line in data:
if '{version}' in line:
fconfig.write(line.format(version=__version__))
i += 1
break
else:
fconfig.write(line)
i += 1
for line in data[i:]:
fconfig.write(line)
fconfig.truncate()
_log_message(_logger, "WARNING",
"Updating the configuration file, your previous "
"settings have be overwritten.")
_load_config(_new_config)
# multithreading
_config["omp"] = int(_os.environ.get("OMP", 1))
if _config["omp"] > 1:
_config["multithreading"] = True
# --------------------- #
# Loading graph library #
#---------------------- #
from .lib.graph_backends import use_backend, analyze_graph
_libs = ['graph-tool', 'igraph', 'networkx']
_glib = _config['backend']
assert _glib in _libs or _glib == 'nngt', \
"Internal error for graph library loading, please report " +\
"this on GitHub."
try:
use_backend(_config['backend'], False, silent=True)
except ImportError:
idx = _libs.index(_config['backend'])
del _libs[idx]
keep_trying = True
while _libs and keep_trying:
try:
use_backend(_libs[-1], False, silent=True)
keep_trying = False
except ImportError:
_libs.pop()
if not _libs:
use_backend('nngt', False, silent=True)
_log_message(_logger, "WARNING",
"This module needs one of the following graph libraries to "
"study networks: `graph_tool`, `igraph`, or `networkx`.")
# ------- #
# Modules #
# ------- #
# import some tools into main namespace
from .io.graph_loading import load_from_file
from .io.graph_saving import save_to_file
from .lib.rng_tools import seed
from .lib.test_functions import on_master_process, num_mpi_processes
from .core.group_structure import Group, MetaGroup, Structure
from .core.neural_pop_group import (GroupProperty, MetaNeuralGroup,
NeuralGroup, NeuralPop)
from .core.graph import Graph
from .core.spatial_graph import SpatialGraph
from .core.networks import Network, SpatialNetwork
from .generation.graph_connectivity import generate
# import modules
from . import analysis
from . import core
from . import generation
from . import geometry
from . import io
from . import lib
__all__ = [
"analysis",
"analyze_graph",
"core",
"generate",
"generation",
"geometry",
"get_config",
"Graph",
"GroupProperty",
"lib",
"load_from_file",
"Network",
"NeuralGroup",
"NeuralPop",
"num_mpi_processes",
"on_master_process",
"save_to_file",
"seed",
"set_config",
"SpatialGraph",
"SpatialNetwork",
"use_backend",
"__version__"
]
# test geometry supports
try:
import svg.path as _svg
_has_svg = True
except:
_has_svg = False
try:
import dxfgrabber as _dxf
_has_dxf = True
except:
_has_dxf = False
try:
import shapely as _shapely
_has_shapely = _shapely.__version__
except:
_has_shapely = False
# test if plot module is supported
try:
from . import plot
_config['with_plot'] = True
__all__.append('plot')
except ImportError as e:
_log_message(_logger, "DEBUG",
"An error occured, plot module will not be loaded: " + str(e))
_config['with_plot'] = False
# lazy load for simulation module
if _config['load_nest'] and _imputil.find_spec("nest") is not None:
_config['with_nest'] = True
simulation = _lazy_load("nngt.simulation")
__all__.append("simulation")
# lazy load for geospatial module
_has_geospatial = False
_has_geopandas = _imputil.find_spec("geopandas")
if _config["load_gis"] is not None and _has_shapely:
geospatial = _lazy_load("nngt.geospatial")
__all__.append("geospatial")
_has_geospatial = True
# load database module if required
if _config["use_database"]:
try:
from . import database
__all__.append('database')
except ImportError as e:
_log_message(_logger, "DEBUG",
"Could not load database module: " + str(e))
# ------------------------ #
# Print config information #
# ------------------------ #
_glib_version = (_config["library"].__version__[:5]
if _config["library"] is not None else __version__)
_log_info = _config_info.format(
gl = _config["backend"] + ' ' + _glib_version,
thread = _config["multithreading"],
plot = _config["with_plot"],
nest = _config["with_nest"],
db =_config["use_database"],
omp = _config["omp"],
s = "s" if _config["omp"] > 1 else "",
mpi = _config["mpi"],
shapely = _has_shapely,
svg = _has_svg,
dxf = _has_dxf,
geotool = _has_geospatial,
)
_log_conf_changed(_log_info)