""" A script for generating HTML docs from LEMS descriptions of the core NeuroML 2 Component Types """ import textwrap from decimal import Decimal from decimal import getcontext from lems.model.model import Model from lems.model.dynamics import OnStart from lems.model.dynamics import OnCondition from lems.model.dynamics import OnEvent from lems.model.dynamics import OnEntry from lems.model.dynamics import Transition from lems.model.dynamics import StateAssignment from lems.model.dynamics import EventOut import math # To display correct conversion values, we limit the precision context to 2 # places (required by Hz). Higher precisions, such as the default machine # precision include the usual issues with floating point arithmetic and do not # display exact conversions # # References: https://docs.python.org/3/tutorial/floatingpoint.html # https://docs.python.org/3/library/decimal.html#module-decimal getcontext().prec = 7 nml2_version = "2.2" nml2_branch = "master" col_width_left = "70" col_width_right = "100" spacer2 = "  " spacer3 = "   " spacer4 = "    " spacer8 = spacer4 + spacer4 grey_style = " style=\"color:darkgrey\"" grey_small_style = " style=\"color:darkgrey;font-size:12px\"" grey_style_dark = " style=\"color:dimgrey\"" grey_style_dark_ital = " style=\"color:dimgrey;font-style:italic\"" grey_blue_style = " style=\"color:#85ACE1;font-style:italic\"" grey_small_style_dark = " style=\"color:dimgrey;font-size:12px\"" lems_xml_url = "https://github.com/NeuroML/NeuroML2/blob/%s/NeuroML2CoreTypes/" % nml2_branch #bioportal_url = "http://bioportal.bioontology.org/ontologies/46856/?p=terms&conceptid=cno:" bioportal_url = "https://bioportal.bioontology.org/ontologies/CNO/?p=classes&conceptid=" nml_src = "../NeuroML2CoreTypes" IF = "IF" THEN = "THEN" def category(name, rows=1, type="label-info"): return (textwrap.dedent( """ {} """.format(col_width_left, rows, type, name) ) ) def exposed_as(name): if name is None: return "" return " (exposed as " + name + ")\n" def dimension(name, pre="", post=""): if name is None or name == "none": return "" + pre + "Dimensionless" + post return "" + pre + "" + name + "" + post + "" def format_expression(expr): expr2 = expr.replace(".gt.", ">") expr2 = expr2.replace(".geq.", ">=") expr2 = expr2.replace(".lt.", "<") expr2 = expr2.replace(".leq.", "<=") expr2 = expr2.replace(".and.", "AND") expr2 = expr2.replace(".eq.", "=") expr2 = expr2.replace(".neq.", "!=") return expr2 def replace_underscores_and_urls(text, useHtml=True): words = text.split(" ") text2 = "" for word in words: if len(word) > 0: if word.startswith("*"): if useHtml: word = "%s"%(word[1:]) if word.endswith("*"): if useHtml: word = "%s" % (word[:-1]) if word.startswith("http://"): if useHtml: word = "%s" % (word, word) elif word.count('_') == 2: pre = word[0:word.find('_')] ct = word[word.find('_') + 1:word.rfind('_')] post = word[word.rfind('_') + 1:] if useHtml: word = pre + "" + comp_type_link(ct) + "" + post else: word = pre + ct + post elif word[0] == '_': if useHtml: word = "%s" % word[1:] else: word = word[1:] text2 = text2 + word + " " return text2 def format_description(element): if element is None or (not isinstance(element, str) and (element.description is None or len(element.description) == 0)): return "" if isinstance(element, str): desc = element else: desc = element.description desc2 = replace_underscores_and_urls(desc, useHtml=True) return "%s" % (grey_style_dark, desc2) def format_description_small(element): if isinstance(element, str): desc = element elif element.description is None: return "" else: desc = element.description if len(desc) == 0: return "" return "
%s%s" % (spacer4, grey_small_style_dark, desc) files = ["Cells", "Synapses", "Channels", "Inputs", "Networks", "PyNN", "NeuroMLCoreDimensions", "NeuroMLCoreCompTypes"] comp_types = {} comp_type_src = {} comp_type_desc = {} ordered_comp_types = {} for file in files: fullfile = "%s/%s.xml" % (nml_src, file) print("\n---------- Reading LEMS file: " + fullfile) model = Model(include_includes=False) model.import_from_file(fullfile) for comp_type in model.component_types: comp_types[comp_type.name] = comp_type comp_type_src[comp_type.name] = file comp_type_desc[comp_type.name] = comp_type.description if comp_type.description is not None else "ComponentType: " + comp_type.name ordered_comp_type_list = [] with open(fullfile) as fp: for line in fp: s = '%s" % (comp_type_src[name], name, desc, compName) def get_extended_from_comp_type(comp_type_name): if comp_type_name not in comp_types: return None extCompTypeName = comp_types[comp_type_name].extends if extCompTypeName is None: return None return comp_types[extCompTypeName] def add_comp_type_and_related(comp_type, added, indent, pre, nameInfo=""): name = comp_type.name extender_pre = "> " % grey_style child_pre = "+ " % grey_style children_pre = "++ " % grey_style contents = "" if name not in added or nameInfo != "": contents += indent + pre + nameInfo + comp_type_link(name) + "
\n \n" added.append(name) for ct in model.component_types: if ct.extends == name: contents += add_comp_type_and_related(ct, added, indent + spacer3, extender_pre) ''' for child in comp_type.getChild(): nameInfo= "" if child.name == child.type else child.name+" " contents += add_comp_type_and_related(comp_types[child.type], added, indent+spacer3, child_pre, nameInfo=nameInfo) ''' for child_or_children in comp_type.children: nameInfo = "" if child_or_children.name == child_or_children.type else child_or_children.name + " " pre = children_pre if child_or_children.multiple else child_pre ctype = child_or_children.type ctype = ctype.replace('rdf:', 'rdf_') contents += add_comp_type_and_related(comp_types[ctype], added, indent + spacer3, pre, nameInfo=nameInfo) return contents for file in files: fullfile = "%s/%s.xml" % (nml_src, file) print("\n---------- Reading LEMS file: " + fullfile) model = Model(include_includes=False) model.import_from_file(fullfile) # contents = HTMLgen.Simplecontentsument(title=file) doc = open("%s.html" % file, 'w') contents = ("\n \n %s\n" + \ " \n" + \ " \n" + \ " \n" + \ " \n\n\n") % file contents += "
\n" + \ "
\n" + \ "
\n" + \ " \n" + \ " \n" + \ " \n" + \ " \n" + \ " \n" + \ " NeuroML v%s Component Types\n" % nml2_version + \ "
\n" + \ "
    \n" for file2 in files: active = " class=\"active\"" if file2 == file else "" contents += " " + file2 + "\n" contents += "
\n" + \ "
\n" + \ "
\n" + \ "
\n" + \ "
\n" + \ "
\n" + \ "
\n" + \ "
\n" + \ "
\n" if len(model.dimensions) > 0: contents += " Dimensions
\n" dimensions = model.dimensions dimensions = sorted(dimensions, key=lambda dim: dim.name) for dim in dimensions: contents += "   " + dimension(dim.name) + "
\n" if len(model.units) > 0: contents += "
Units
\n" units = model.units units = sorted(units, key=lambda unit: unit.symbol) for unit in units: contents += "   " + dimension(unit.symbol) + "
\n" if len(model.component_types) > 0: contents += " Component Types
\n" added = [] for o_comp_type in ordered_comp_types[file]: o_comp_type = o_comp_type.replace('rdf:', 'rdf_') comp_type = model.component_types[o_comp_type] contents += add_comp_type_and_related(comp_type, added, "", "") contents += "
\n" + \ "
\n" + \ "
\n" desc = "NeuroML2 ComponentType definitions from %s.xml" % file if model.description: desc = model.description contents += ('

NOTE: the latest version of this documentation can be found on docs.neuroml.org!


For more information on NeuroML 2 and LEMS see here.
Note: these descriptions have been updated to the latest ' " NeuroML v%s definitions, using " " the latest version of LEMS!
\n" + \ " \n" + \ " \n" + \ " \n" + \ "

%s

%s
Original LEMS ComponentType definitions: %s.xml
" + \ " Schema against which NeuroML based on these should be valid: NeuroML_v%s.xsd

\n") % (nml2_branch, nml2_version, file, format_description(desc), lems_xml_url, file, file, nml2_branch, nml2_version, nml2_version) ''' for inc in model.getInclude(): contents += "

Included file: %s

\n"%(inc.getFile().replace(".xml", ".html"),inc.getFile())''' if "Dimensions" in file: dimensions = model.dimensions dimensions = sorted(dimensions, key=lambda dim: dim.name) for dim in dimensions: contents += " \n" contents += "\n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += "
\n" contents += " " + dim.name + "\n" contents += "
\n" contents2 = "" format = "%s%i " if dim.m is not None and dim.m != 0: contents += format % ("M", dim.m) if dim.l is not None and dim.l != 0: contents += format % ("L", dim.l) if dim.t is not None and dim.t != 0: contents += format % ("T", dim.t) if dim.i is not None and dim.i != 0: contents += format % ("I", dim.i) if dim.k is not None and dim.k != 0: contents += format % ("K", dim.k) if dim.n is not None and dim.n != 0: contents += format % ("N", dim.n) contents += "
" for unit in units: if unit.dimension == dim.name: contents += "
" + spacer4 + "Defined unit: %s" % (unit.symbol, unit.symbol) + "\n" contents += "
\n" units = model.units units = sorted(units, key=lambda unit: unit.symbol) for unit in units: contents += " \n" contents += "\n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += " \n" contents += "
\n" contents += " " + unit.symbol + "\n" contents += "
\n" offset = "" if unit.offset is not None and unit.offset != 0: offset = "
" + spacer4 + "Offset: " + str(unit.offset) scale = "" if unit.scale is not None and unit.scale != 1: scale = "
" + spacer4 + "Scale: " + str(unit.scale) contents += spacer4 + "Dimension: " + dimension(unit.dimension, "", "") + "
" + spacer4 + "Power of 10: " + str(unit.power) + offset + scale + "
\n" for unit2 in model.units: if unit.symbol != unit2.symbol and unit.dimension == unit2.dimension: si_val = model.get_numeric_value("1%s" % unit.symbol, unit.dimension) unit_val = ((Decimal(si_val)/Decimal(math.pow(10,unit2.power))) / Decimal(unit2.scale))-Decimal(unit2.offset) scaled = float(unit_val) # to catch 60.0001 etc. if scaled>1 and int(scaled)!=scaled: if scaled-int(scaled)<0.001: scaled = int(scaled) if scaled>10000: scaled = '%.2e'%scaled else: scaled = '%s'%scaled if scaled.endswith('.0'): scaled = scaled[:-2] #contents += "
" + spacer4 + "1 %s = %s %s 1: %s, 2: %s, si_val: %s, Decimal(unit2.scale): %s, unit_val: %s, Decimal(math.pow(10,unit2.power)): %s" % (unit.symbol, "%s" % scaled, unit2.symbol, unit2.symbol, factor1, factor2, si_val, Decimal(unit2.scale), unit_val, Decimal(math.pow(10,unit2.power))) contents += "
" + spacer4 + "1 %s = %s %s" % (unit.symbol, "%s" % scaled, unit2.symbol, unit2.symbol) contents += "
\n" for o_comp_type in ordered_comp_types[file]: o_comp_type = o_comp_type.replace('rdf:', 'rdf_') comp_type = model.component_types[o_comp_type] # print "ComponentType %s is %s"%(comp_type.name, comp_type.description) ext = "" if comp_type.extends is None else "

%sextends %s

" % (spacer4, comp_type_link(comp_type.extends)) # ext = "" if comp_type.extends is None else "%sextends %s"%(spacer4,comp_type.extends,comp_type.extends) contents += " \n" contents += "\n" contents += " \n" contents += " \n" contents += " \n" desc = "--- no description yet ---" if comp_type.description is None else format_description(comp_type) cnoLink = "" if " cno_00" in str(comp_type.description): cno = comp_type.description.split(" ")[-1] desc = desc.replace(cno, "") title = "Link to Bioportal entry for Computational Neuroscience Ontology related to: " + comp_type.name cnoLink = "

%s" % (title, bioportal_url, cno, cno) contents += " \n" contents += " \n" contents += " \n" params = {} derived_params = {} texts = {} paths = {} exposures = {} requirements = {} eventPorts = {} for param in comp_type.parameters: params[param] = comp_type.name for derived_param in comp_type.derived_parameters: derived_params[derived_param] = comp_type.name for text in comp_type.texts: texts[text] = comp_type.name for path in comp_type.paths: paths[path] = comp_type.paths for exp in comp_type.exposures: exposures[exp] = comp_type.name for req in comp_type.requirements: requirements[req] = comp_type.name for ep in comp_type.event_ports: eventPorts[ep] = comp_type.name extd_comp_type = get_extended_from_comp_type(comp_type.name) while extd_comp_type is not None: for param in extd_comp_type.parameters: pk = params.copy().keys() for pp0 in pk: if pp0.name == param.name: del params[pp0] params[param] = extd_comp_type.name for derived_param in extd_comp_type.derived_parameters: derived_params[derived_param] = extd_comp_type.name for text in extd_comp_type.texts: texts[text] = extd_comp_type.name for path in extd_comp_type.paths: paths[path] = extd_comp_type.paths for exp in extd_comp_type.exposures: ek = exposures.copy().keys() for ee0 in ek: if ee0.name == exp.name: del exposures[ee0] exposures[exp] = extd_comp_type.name for req in extd_comp_type.requirements: requirements[req] = extd_comp_type.name for ep in extd_comp_type.event_ports: eventPorts[ep] = extd_comp_type.name extd_comp_type = get_extended_from_comp_type(extd_comp_type.name) if len(params) > 0: contents += " \n" contents += category("Parameters", len(params), type="label-success") keysort = sorted(params.keys(), key=lambda param: param.name) # print keysort for param in keysort: ct = params[param] origin = format_description_small(param) style = "" if ct is not comp_type.name: origin = spacer4 + "(from " + comp_type_link(ct) + ")" style = grey_style contents += " " + param.name + "" + origin + "\n \n \n" if len(derived_params) > 0: contents += " \n" contents += category("Derived Parameters", len(derived_params), type="label-success") keysort = sorted(derived_params.keys(), key=lambda derived_param: derived_param.name) for dp in keysort: ct = derived_params[dp] origin = "" style = "" if ct is not comp_type.name: origin = spacer4 + "(from " + comp_type_link(ct) + ")" style = grey_style contents += " " + dp.name + " = " + dp.value + "" + origin + "\n \n \n" if len(comp_type.texts) > 0: # TODO: Check if Text elements are inherited... contents += " \n" contents += category("Text fields", len(comp_type.texts), type="label-success") for text in comp_type.texts: contents += " \n \n" if len(comp_type.paths) > 0: # TODO: Check if Path elements are inherited... contents += " \n" contents += category("Paths", len(comp_type.paths), type="label-success") for path in comp_type.paths: contents += " \n \n" # TODO: check if ComponentRef are inherited... if len(comp_type.component_references) > 0: contents += " \n" contents += category("Component References", len(comp_type.component_references), type="label-success") for cr in comp_type.component_references: contents += " \n \n \n" # TODO: check if Childrens are inherited... if len(comp_type.children) > 0: contents += " \n" child_contents = "" children_contents = "" child_count = 0 children_count = 0 # contents += category("Children elements", len(comp_type.children), type="label-success") for child_or_children in comp_type.children: if not child_or_children.multiple: child_count += 1 child_contents += " \n \n \n" else: children_count += 1 children_contents += " \n \n \n" if child_count > 0: contents += category("Child elements", child_count, type="label-success") contents += child_contents if children_count > 0: contents += category("Children elements", children_count, type="label-success") contents += children_contents if len(comp_type.constants) > 0: contents += " \n" contents += category("Constants", len(comp_type.constants)) for const in comp_type.constants: contents += " \n \n \n" if len(exposures) > 0: contents += " \n" contents += category("Exposures", len(exposures)) for exp in sorted(exposures, key=lambda entry: entry.name): ct = exposures[exp] origin = format_description_small(exp) style = "" if ct is not comp_type.name: origin = spacer4 + "(from " + comp_type_link(ct) + ")" style = grey_style contents += " " + exp.name + "" + origin + "\n \n \n" if len(requirements) > 0: contents += " \n" contents += category("Requirements", len(requirements)) for req in sorted(requirements, key=lambda entry: entry.name): ct = requirements[req] origin = format_description_small(req) style = "" if ct is not comp_type.name: origin = spacer4 + "(from " + comp_type_link(ct) + ")" style = grey_style contents += " " + req.name + "" + origin + "\n \n \n" if len(eventPorts) > 0: contents += " \n" contents += category("Event Ports", len(eventPorts)) for ep in sorted(eventPorts, key=lambda entry: entry.name): ct = eventPorts[ep] origin = format_description_small(ep) style = "" if ct is not comp_type.name: origin = spacer4 + "(from " + comp_type_link(ct) + ")" style = grey_style contents += " " + ep.name + "" + origin + "\n \n \n" # TODO: check if Attachments are inherited... if len(comp_type.attachments) > 0: contents += " \n" contents += category("Attachments", len(comp_type.attachments)) for att in comp_type.attachments: contents += " \n \n \n" if comp_type.dynamics and comp_type.dynamics.has_content(): dynamics = comp_type.dynamics contents += "\n" contents += category("Dynamics", type="") contents += "\n" contents += "\n" contents += "
\n" classInfo = "" if comp_type.name.startswith("base"): contents += " " + comp_type.name + "\n" else: contents += " " + comp_type.name + "\n" contents += ext contents += "
\n" contents += " " + desc + cnoLink contents += "
" + dimension(param.dimension) + "
" + dimension(dp.dimension) + "
" + text.name + "
" + path.name + "
" + cr.name + "" + comp_type_link(cr.type) + "
" + child_or_children.name + "" + comp_type_link(child_or_children.type) + "
" + child_or_children.name + "" + comp_type_link(child_or_children.type) + "
" + const.name + " = " + const.value + format_description_small(const) + "" + dimension(const.dimension) + "
" + dimension(exp.dimension) + "
" + dimension(req.dimension) + "
Direction: " + ep.direction + "
" + att.name + "" + comp_type_link(att.type) + "
\n" structure = None if comp_type.structure is not None: structure = comp_type.structure if structure is not None and \ len(structure.withs) + len(structure.child_instances) + \ len(structure.multi_instantiates) + len(structure.event_connections) > 0: contents += "Structure

\n" for w in structure.withs: contents += spacer4 + "WITH " + w.instance + " AS " + w.as_ + "
\n" for ci in structure.child_instances: contents += spacer4 + "CHILD INSTANCE: " + ci.component + "
\n" for mi in structure.multi_instantiates: contents += spacer4 + "INSTANTIATE " + mi.number + " COMPONENTS OF TYPE " + mi.component + "
\n" for ec in structure.event_connections: targetPort = "" if ec.target_port is None else ", TARGET PORT: " + ec.target_port + "" receiver = "" if ec.receiver is None else ", RECEIVER: " + ec.receiver + "" delay = "" if not hasattr(ec, 'delay') or ec.delay is None else ", DELAY: " + ec.delay + "" contents += spacer4 + "EVENT CONNECTION from " + ec.from_ + " TO " + ec.to + "" + receiver + targetPort + delay + "
\n" if hasattr(ec, 'assign') and ec.assign is not None: contents += spacer4 + spacer4 + "ASSIGN " + ec.assign.property + " = " + ec.assign.value + "
\n" contents += "
\n" if len(dynamics.state_variables) > 0: contents += "State Variables

\n" for sv in dynamics.state_variables: contents += spacer4 + "" + sv.name + "" + spacer4 + dimension(sv.dimension) + exposed_as(sv.exposure) + "
\n" if len(dynamics.state_variables) > 0: contents += "
\n" if dynamics.event_handlers is not None: os_content = "" oc_content = "" oe_content = "" for eh in dynamics.event_handlers: if isinstance(eh, OnStart): if len(os_content) == 0: os_content += "On Start

\n" os = eh for ac in os.actions: if isinstance(ac, StateAssignment): os_content += spacer4 + "" + ac.variable + " = " + ac.value + "
\n" os_content += "
\n" if isinstance(eh, OnCondition): if len(oc_content) == 0: oc_content += "On Conditions

\n" oc = eh test = format_expression(oc.test) oc_content += spacer4 + IF + " " + test + " " + THEN + "
\n" for ac in oc.actions: if isinstance(ac, StateAssignment): oc_content += spacer4 + spacer4 + "" + ac.variable + " = " + ac.value + "
\n" if isinstance(ac, EventOut): oc_content += spacer4 + spacer4 + "EVENT OUT on port " + ac.port + "
\n" oc_content += "
\n" if isinstance(eh, OnEvent): if len(oe_content) == 0: oe_content += "On Events

\n" oe = eh oe_content += spacer4 + "EVENT IN on port: " + oe.port + "
\n" for ac in oe.actions: if isinstance(ac, StateAssignment): oe_content += spacer4 + spacer4 + "" + ac.variable + " = " + ac.value + "
\n" if isinstance(ac, EventOut): oe_content += spacer4 + spacer4 + "EVENT OUT on port " + ac.port + "
\n" oe_content += "
\n" contents += os_content contents += oc_content contents += oe_content if len(dynamics.derived_variables) > 0: contents += "Derived Variables

\n" for dv in dynamics.derived_variables: res = dv.value if res is None: res = str.replace(dv.select, '/', '->') if dv.reduce: res = res + " (reduce method: " + dv.reduce + ")" contents += spacer4 + "" + dv.name + " = " + res + spacer4 + exposed_as(dv.exposure) + "
\n" if len(dynamics.derived_variables) > 0: contents += "
\n" if len(dynamics.conditional_derived_variables) > 0: contents += "Conditional Derived Variables

\n" for cdv in dynamics.conditional_derived_variables: for case in cdv.cases: res = case.value cond = IF + " " + format_expression(case.condition) + " " + THEN + "
" + spacer4 + spacer4 if case.condition else "OTHERWISE
" + spacer4 + spacer4 contents += spacer4 + cond + "" + cdv.name + " = " + res + spacer4 + exposed_as(cdv.exposure) + "
\n" contents += "
\n" if len(dynamics.conditional_derived_variables) > 0: contents += "
\n" if len(dynamics.time_derivatives) > 0: contents += "Time Derivatives

\n" for td in dynamics.time_derivatives: contents += spacer4 + "d " + td.variable + " /dt = " + td.value + "
\n" if len(dynamics.time_derivatives) > 0: contents += "
\n" if len(dynamics.regimes) > 0: for rg in dynamics.regimes: initial = "" if rg.initial is None or rg.initial == "false" else " (initial)" contents += "Regime: " + rg.name + initial + "

\n" oc_content = "" for eh in rg.event_handlers: if isinstance(eh, OnEntry): contents += spacer8 + "On Entry

\n" oe = eh for ac in oe.actions: if isinstance(ac, StateAssignment): contents += spacer8 + spacer4 + "" + ac.variable + " = " + ac.value + "
\n" contents += "
\n" if isinstance(eh, OnCondition): if len(oc_content) == 0: oc_content += spacer8 + "On Conditions

\n" oc = eh test = format_expression(oc.test) oc_content += spacer8 + spacer4 + IF + " " + test + " " + THEN + "
\n" for ac in oc.actions: if isinstance(ac, StateAssignment): oc_content += spacer8 + spacer4 + spacer4 + "" + ac.variable + " = " + ac.value + "
\n" if isinstance(ac, EventOut): oc_content += spacer8 + spacer4 + spacer4 + "EVENT OUT on port " + ac.port + "
\n" if isinstance(ac, Transition): oc_content += spacer8 + spacer4 + spacer4 + "TRANSITION to REGIME " + ac.regime + "
\n" oc_content += "
\n" contents += oc_content if len(rg.time_derivatives) > 0: contents += spacer8 + "Time Derivatives

\n" for td in rg.time_derivatives: contents += spacer8 + spacer4 + "d " + td.variable + " /dt = " + td.value + "
\n" if len(rg.time_derivatives) > 0: contents += "
\n" if len(dynamics.regimes) > 0: contents += "
\n" contents += "
\n\n" contents += "
\n" contents += "
\n" contents += "
\n" contents += "\n" for line in contents.split('\n'): # print("Writing: "+line) doc.write(line + '\n') doc.close()