# Copyright 2023-2024 Blue Brain Project / EPFL
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Factory that separates Synapse creation from Synapse instances."""
from __future__ import annotations # PEP-563 forward reference annotations
from enum import Enum
import logging
import math
import neuron
import numpy as np
import pandas as pd
import bluecellulab
from bluecellulab.exceptions import BluecellulabError
from bluecellulab.synapse import Synapse, GabaabSynapse, AmpanmdaSynapse, GluSynapse
from bluecellulab.circuit.config.sections import Conditions
from bluecellulab.circuit.synapse_properties import SynapseProperties, SynapseProperty
from bluecellulab.synapse.synapse_types import SynapseHocArgs
from bluecellulab.type_aliases import NeuronSection
SynapseType = Enum("SynapseType", "GABAAB AMPANMDA GLUSYNAPSE")
logger = logging.getLogger(__name__)
[docs]
class SynapseFactory:
"""Creates different types of Synapses."""
[docs]
@classmethod
def create_synapse(
cls,
cell: bluecellulab.Cell,
syn_id: tuple[str, int],
syn_description: pd.Series,
condition_parameters: Conditions,
popids: tuple[int, int],
extracellular_calcium: float | None,
connection_modifiers: dict,
) -> Synapse:
"""Returns a Synapse object."""
syn_type = cls.determine_synapse_type(syn_description)
syn_hoc_args = cls.determine_synapse_location(syn_description, cell)
synapse: Synapse
if syn_type == SynapseType.GABAAB:
if condition_parameters.randomize_gaba_rise_time is not None:
randomize_gaba_risetime = condition_parameters.randomize_gaba_rise_time
else:
randomize_gaba_risetime = True
synapse = GabaabSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description,
popids, cell.post_gid, extracellular_calcium, randomize_gaba_risetime)
elif syn_type == SynapseType.AMPANMDA:
synapse = AmpanmdaSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description,
popids, cell.post_gid, extracellular_calcium)
else:
synapse = GluSynapse(cell.cell_id, syn_hoc_args, syn_id, syn_description,
popids, cell.post_gid, extracellular_calcium)
synapse = cls.apply_connection_modifiers(connection_modifiers, synapse)
return synapse
@staticmethod
def apply_connection_modifiers(connection_modifiers: dict, synapse: Synapse) -> Synapse:
if "DelayWeights" in connection_modifiers:
synapse.delay_weights = connection_modifiers["DelayWeights"]
if "Weight" in connection_modifiers:
synapse.weight = connection_modifiers["Weight"]
if "SynapseConfigure" in connection_modifiers:
synapse.apply_hoc_configuration(connection_modifiers["SynapseConfigure"])
return synapse
[docs]
@staticmethod
def determine_synapse_type(
syn_description: pd.Series,
) -> SynapseType:
"""Returns the type of synapse to be created."""
is_inhibitory: bool = int(syn_description[SynapseProperty.TYPE]) < 100
all_plasticity_props_available: bool = all(
x in syn_description and syn_description[x] is not None and not math.isnan(syn_description[x])
for x in SynapseProperties.plasticity
)
no_plasticity_props_available: bool = all(
x not in syn_description or syn_description[x] is None or math.isnan(syn_description[x])
for x in SynapseProperties.plasticity
)
if is_inhibitory:
return SynapseType.GABAAB
else:
if all_plasticity_props_available:
return SynapseType.GLUSYNAPSE
elif no_plasticity_props_available:
return SynapseType.AMPANMDA
else:
raise BluecellulabError("SynapseFactory: Cannot determine synapse type")
[docs]
@classmethod
def determine_synapse_location(cls, syn_description: pd.Series, cell: bluecellulab.Cell) -> SynapseHocArgs:
"""Returns the location of the synapse."""
isec = int(syn_description[SynapseProperty.POST_SECTION_ID]) # numpy int to int
section: NeuronSection = cell.get_psection(section_id=isec).hsection
# old circuits don't have it, it needs to be computed via synlocation_to_segx
if (SynapseProperty.AFFERENT_SECTION_POS in syn_description and
not np.isnan(syn_description[SynapseProperty.AFFERENT_SECTION_POS])):
# position is pre computed in SONATA
location = syn_description[SynapseProperty.AFFERENT_SECTION_POS]
if location == 0.0:
location = 0.0000001
elif location >= 1.0:
location = 0.9999999
else:
ipt = syn_description[SynapseProperty.POST_SEGMENT_ID]
syn_offset = syn_description[SynapseProperty.POST_SEGMENT_OFFSET]
location = cls.synlocation_to_segx(section, ipt, syn_offset)
return SynapseHocArgs(location, section)
[docs]
@staticmethod
def synlocation_to_segx(
section: NeuronSection, ipt: float, syn_offset: float
) -> float:
"""Translates a synaptic (secid, ipt, offset) to an x coordinate.
Args:
section: Section object.
ipt: Post segment ID.
syn_offset: Synaptic offset.
Returns:
The x coordinate on the section, where the synapse can be placed.
"""
if syn_offset < 0.0:
syn_offset = 0.0
length = section.L
# access section to compute the distance
if neuron.h.section_orientation(sec=section) == 1:
ipt = neuron.h.n3d(sec=section) - 1 - ipt
syn_offset = -syn_offset
distance = 0.5
if ipt < neuron.h.n3d(sec=section):
distance = (neuron.h.arc3d(ipt, sec=section) + syn_offset) / length
if distance == 0.0:
distance = 0.0000001
if distance >= 1.0:
distance = 0.9999999
if neuron.h.section_orientation(sec=section) == 1:
distance = 1 - distance
if distance < 0:
logger.warning(
f"synlocation_to_segx found negative distance \
at curr_sec({neuron.h.secname(sec=section)}) syn_offset: {syn_offset}"
)
return 0.0
else:
return distance