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
8 changes: 7 additions & 1 deletion bluepyemodel/access_point/forge_access_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from bluepyemodel.emodel_pipeline.emodel_settings import EModelPipelineSettings
from bluepyemodel.emodel_pipeline.emodel_workflow import EModelWorkflow
from bluepyemodel.emodel_pipeline.memodel import MEModel
from bluepyemodel.emodel_pipeline.simulatable_neuron import SimulatableNeuron
from bluepyemodel.evaluation.fitness_calculator_configuration import FitnessCalculatorConfiguration
from bluepyemodel.model.distribution_configuration import DistributionConfiguration
from bluepyemodel.model.neuron_model_configuration import NeuronModelConfiguration
Expand All @@ -57,6 +58,7 @@
"EModelWorkflow": "EModelWorkflow",
"EModelScript": "EModelScript",
"MEModel": "MEModel",
"SimulatableNeuron": "SimulatableNeuron",
}

CLASS_TO_RESOURCE_NAME = {
Expand All @@ -69,6 +71,7 @@
"EModelWorkflow": "EMW",
"EModelScript": "EMS",
"MEModel": "MEM",
"SimulatableNeuron": "SN",
}

NEXUS_TYPE_TO_CLASS = {
Expand All @@ -81,6 +84,7 @@
"EModelWorkflow": EModelWorkflow,
"EModelScript": EModelScript,
"MEModel": MEModel,
"SimulatableNeuron": SimulatableNeuron,
}

NEXUS_ENTRIES = [
Expand Down Expand Up @@ -437,7 +441,9 @@ def register(
# when EModelWorkflow resource is complete
if type_ == "EModelWorkflow":
type_ = "Entity"
schema_id = self.forge._model.schema_id(type_)
schema_id = None
if type_ != "SimulatableNeuron":
schema_id = self.forge._model.schema_id(type_)

self.forge.register(resource, schema_id=schema_id)

Expand Down
51 changes: 51 additions & 0 deletions bluepyemodel/access_point/nexus.py
Original file line number Diff line number Diff line change
Expand Up @@ -1486,3 +1486,54 @@ def sonata_exists(self, seed):
return True
except AccessPointException:
return False

def store_simulatable_neuron(self, simulatable_neuron, is_analysis_suitable=False):
"""Store a BPEM object on Nexus

Args:
simulatable_neuron (SimulatableNeuron)
is_analysis_suitable (bool): Should be True only when managing metatada for resources
of type EModel, for which all data are complete (has FCC, ETC, EMC, etc.).
"""

metadata_dict = self.emodel_metadata_ontology.for_resource(
is_analysis_suitable=is_analysis_suitable
)

type_ = "SimulatableNeuron"

base_payload = simulatable_neuron.as_dict()
base_payload["type"] = ["Entity", type_]
if "subject" in metadata_dict:
base_payload["subject"] = metadata_dict["subject"]
if "brainLocation" in metadata_dict:
base_payload["brainLocation"] = metadata_dict["brainLocation"]
if "annotation" in metadata_dict:
base_payload["annotation"] = metadata_dict["annotation"]

self.access_point.register(
base_payload,
filters_existence=None,
legacy_filters_existence=None,
replace=False,
distributions=None,
images=None,
type_=type_,
)

# update EMW if any
workflow, workflow_id = self.get_emodel_workflow()

# wait for the object to be uploaded and fetchable
if workflow is not None and workflow_id is not None:
time.sleep(self.sleep_time)

# fetch just uploaded simulatable neuron resource to get its id
type_ = "SimulatableNeuron"
filters = {"type": type_, **simulatable_neuron.as_dict()}
resource = self.access_point.fetch_one(filters, strict=True)
simulatable_neuron_id = resource.id
workflow.add_simulatable_neuron_id(simulatable_neuron_id)

time.sleep(self.sleep_time)
self.store_or_update_emodel_workflow(workflow)
15 changes: 13 additions & 2 deletions bluepyemodel/emodel_pipeline/emodel_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(
fitness_configuration_id=None,
emodels=None,
emodel_scripts_id=None,
simulatable_neuron_ids=None,
state="not launched",
):
"""Init
Expand All @@ -42,6 +43,8 @@ def __init__(
emodel_configuration (str): NeuronModelConfiguration id
fitness_configuration_id (str): FitnessCalculatorConfiguration id
emodels (list): list of EModel ids
emodel_scripts_id (list): list of EModelScript ids
simulatable_neuron_ids (list): list of SimulatableNeuron ids
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is a list because you want to add all the related resource IDs of SimulatableNeuron to one list without any order or names. Do you not want to distinguish between different IDs of different resource types: morphology, mechanisms, etc.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a list because a workflow can produce multiple emodels (using multiple seeds)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mechanisms id, morphology id are listed in simulatable neuron class. They are also present in emodel configuration

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a list because a workflow can produce multiple emodels (using multiple seeds)

Oh, alright. So, to access the best one e.g. similar to one generate hoc function, do you plan to create a function for SimulatebleNeuron?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We don't do that in generate hoc function

state (str): can be "not launched", "running" or "done"
"""
self.targets_configuration_id = targets_configuration_id
Expand All @@ -50,16 +53,21 @@ def __init__(
self.fitness_configuration_id = fitness_configuration_id
self.emodels = emodels if emodels else []
self.emodel_scripts_id = emodel_scripts_id if emodel_scripts_id else []
self.simulatable_neuron_ids = simulatable_neuron_ids if simulatable_neuron_ids else []
self.state = state

def add_emodel_id(self, emodel_id):
"""Add an emodel id to the list of emodels"""
self.emodels.append(emodel_id)

def add_emodel_script_id(self, emodel_script_id):
"""Add an emodel id to the list of emodels"""
"""Add an emodel script id to the list of emodel scripts"""
self.emodel_scripts_id.append(emodel_script_id)

def add_simulatable_neuron_id(self, simulatable_neuron_id):
"""Add an simulatable neuron id to the list of simulatable neurons"""
self.emodel_scripts_id.append(simulatable_neuron_id)

def get_configuration_ids(self):
"""Return all configuration id parameters"""
ids = (
Expand All @@ -75,7 +83,10 @@ def get_configuration_ids(self):
def get_related_nexus_ids(self):
emodels_ids = [{"id": id_, "type": "EModel"} for id_ in self.emodels]
emodel_scripts_ids = [{"id": id_, "type": "EModelScript"} for id_ in self.emodel_scripts_id]
generates = emodels_ids + emodel_scripts_ids
simulatable_neuron_ids = [
{"id": id_, "type": "SimulatableNeuron"} for id_ in self.simulatable_neuron_ids
]
generates = emodels_ids + emodel_scripts_ids + simulatable_neuron_ids
if self.fitness_configuration_id:
generates.append(
{
Expand Down
62 changes: 62 additions & 0 deletions bluepyemodel/emodel_pipeline/simulatable_neuron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""SimulatableNeuron class"""

"""
Copyright 2025 Open Brain Institute

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.
"""


class SimulatableNeuron:
"""Whole neuron model, including links to morphology, ion channels models and hoc."""

def __init__(
self,
name,
description,
emodel_script_id,
mechanism_ids,
morphology_id,
holding_current=None,
threshold_current=None,
validated=False,
):
"""Init

from_circuit and synaptomes, that might be present in the database,
are not implemented here.
subject, brainRegion are coming emodel_metadata

Args:
name (str): name
description (str): description of the model
emodel_script_id (str): ID of the hoc model in the database
mechanism_ids (list of str): IDs of the ion channel models in the database
morphology_id (str): ID of the morphology in the database
holding_current (float): holding current to use in protocols (in nA)
threshold_current (float): current at which the cell starts firing (in nA)
validated (bool): whether the model has been validated by user
"""
# check if brain region and subject are automatically added
self.name = name
self.description = description
self.emodel_script_id = emodel_script_id
self.mechanism_ids = mechanism_ids
self.morphology_id = morphology_id
self.holding_current = holding_current
self.threshold_current = threshold_current
self.validated = validated

def as_dict(self):
"""Used for the storage of the object"""
return vars(self)