Skip to content

Feat: util to create + include new components in NeuroMLDocuments #430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 23, 2024
Merged
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
75 changes: 75 additions & 0 deletions pyneuroml/utils/components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Utilities for creation and inclusion of new Components in models.

File: pyneuroml/utils/components.py

Copyright 2024 NeuroML contributors
"""

import logging
from typing import Optional

from lems.model.component import Component
from lems.model.model import Model

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


def add_new_component(
component_id: str,
component_type: str,
component_filename: Optional[str] = None,
**kwargs,
) -> Component:
"""Add a new component to a NeuroMLDocument.

This will create a new component with the provided id, type, and keyword
arguments and export it to a separate XML file and add that file as an
includes to the provided NeuroMLDocument object.

This is the suggested way of including new Components of new ComponentTypes
because it keeps them separate from the main model that is composed of
Components from the NeuroML schema---which can be validated. New
ComponentTypes and their Components can be used to extend NeuroML, but
since they are not yet included in the NeuroML standard, they cannot be
validated against the schema.

.. versionadded:: 1.3.13

:param nmldoc: NeuroMLDocument object to include component in
:type nmldoc: NeuroMLDocument
:param component_id: id of new Component
:type component_id: str
:param component_type: name of ComponentType that this Component is an
instance of. This must be a valid ComponentType that has been included
in the model. This function will not check this.
:type component_type: str
:param component_filename: optional file name to store the XML export of
the component in
:type component_filename: str
:param **kwargs: parameters to pass to the Component
:returns: the Component Object, and the name of the XML file it was serialised in
:rtype: tuple(Component, str)
"""
newmodel = Model()
newcomp = Component(id_=component_id, type_=component_type, **kwargs)
newmodel.add_component(newcomp)

if component_filename is None:
component_filename = f"component_{component_id}.xml"

logger.info(
f"Saving component with id '{component_id}' and type '{component_type}' to {component_filename}"
)
newmodel.export_to_file(component_filename)

logger.info(
"Component file included in NeuroML document. Note that new components will not validate against the NeuroML schema."
)
logger.info(
f"Please also remember to include {component_filename} in the LEMS simulation file"
)

return newcomp, component_filename
11 changes: 11 additions & 0 deletions tests/utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import logging
import math
import os
import pathlib as pl

import neuroml
Expand All @@ -20,6 +21,7 @@
rotate_cell,
translate_cell_to_coords,
)
from pyneuroml.utils.components import add_new_component

from .. import BaseTestCase

Expand Down Expand Up @@ -282,3 +284,12 @@ def test_translate_cell_to_coords(self):
write_neuroml2_file(
newdoc, "tests/utils/test_translation.net.nml", validate=True
)

def test_adding_new_components(self):
"""Test add_new_component method."""
new_comp, new_comp_file = add_new_component(
"newcomp", "sometype", param1="5v", param2="something"
)
self.assertIsNotNone(new_comp)
self.assertIsFile(new_comp_file)
os.unlink(new_comp_file)
Loading