fromneko._outputs.exportsimportExportsexporter=Exports(net)# Export to BNet format for MaBoSSexporter.export_bnet("my_model.bnet")# Export to SIF for Cytoscapeexporter.export_sif("my_network.sif")
export_bnet("my_model.bnet") writes numbered files such as
my_model_1.bnet, because each bimodal edge can produce stimulation and
inhibition variants. Opposite parallel regulatory edges are normalized to one
bimodal edge before export. Pass n= to cap the number of variants generated.
SIGNOR group/context labels and other identifiers containing spaces, /, -,
#, or : are sanitized for BNet output. Export raises ValueError if two
different labels would collapse to the same sanitized identifier. Custom nodes,
including compressed GO phenotypes, are resolved using the network's node table
rather than being treated as protein identifiers. Null endpoints and endpoints
missing from that node table are rejected with a descriptive error.
Each output also receives a <filename>.neko.json companion preserving source
state identity, canonical-to-exported identifier mappings, provenance, and retained
history. SIF additionally embeds provenance comments. Keep the JSON companion for
formats such as BNET that cannot carry this metadata. See
Interaction curation and persistence.
Exports(net) captures independent working tables and one history payload when
constructed, without copying resource tables or connection indexes. Later edits,
checkout or pruning of net do not change that export capture. All Boolean
variants reuse the captured history.
When export_bnet writes a single model, its companion embeds the history
(schema version 1). When bimodal interactions produce several variants, the
history is written once to <stem>.bnet.history.json and each variant's
companion references it by file name and SHA-256 (schema version 2), so the
output no longer grows by one full history per variant. Keep the shared file next
to the companions; Network.from_history verifies its digest before loading.
Companions and net.export_history(path) use compact JSON by default, retaining
the same schema and every snapshot/evidence field. Use Exports(net, pretty=True)
or net.export_history(path, pretty=True) for indented JSON. Compact serialization
writes individual states without allocating one full-history JSON string.
Snapshot retention and downstream artifact-size limits still apply.
This class implement many methods used to export the Network object in different format.
In particular the exports format will be methods-oriented (MaBoSS, Ginsim, cobrexa and so on...).
To start with, the user can export the Network in SIF and Bnet format.
In the future many more versatile methods will be implemented (SBML) and annotations will be included for each
interaction, including the DOI of the relative reference and annotations from each database
def__init__(self,network,*,pretty=False):# Capture detached export data once. Copying Network also copies the# provider tables, connection indexes and every historical DataFrame.self._history=history_payload(network)ifhasattr(network,'_states')elseNoneself._pretty=prettydf_edges=copy_frame(network.convert_edgelist_into_genesymbol())self.nodes=copy_frame(network.nodes)self.interactions=consolidate_edges(df_edges)return
defexport_bnet(self,file_name="logic_model.bnet",n=None):""" Function to export the network in bnet format, creating multiple files for bimodal interactions. """# Checks for nodes and interactions dataifnotisinstance(self.nodes,pd.DataFrame)orself.nodes.empty:print("Error: Nodes data is missing or empty.")returnifnotisinstance(self.interactions,pd.DataFrame)orself.interactions.empty:print("Error: Interactions data is missing or empty.")returnrequired_node_columns={'Genesymbol'}required_edge_columns={'source','target','Effect'}missing_node_columns=required_node_columns.difference(self.nodes.columns,)missing_edge_columns=required_edge_columns.difference(self.interactions.columns,)ifmissing_node_columnsormissing_edge_columns:missing=sorted(missing_node_columns|missing_edge_columns)raiseValueError('BNet export is missing required column(s): 'f'{", ".join(missing)}.',)interactions=consolidate_edges(self.interactions)invalid_nodes=self.nodes['Genesymbol'].isna()|self.nodes['Genesymbol'].map(lambdavalue:isinstance(value,str)andnotvalue.strip())invalid_sources=interactions['source'].isna()|interactions['source'].map(lambdavalue:isinstance(value,str)andnotvalue.strip())invalid_targets=interactions['target'].isna()|interactions['target'].map(lambdavalue:isinstance(value,str)andnotvalue.strip())ifinvalid_nodes.any()orinvalid_sources.any()orinvalid_targets.any():raiseValueError('BNet export requires non-empty node labels and interaction ''endpoints; found a null or empty identifier.',)node_labels=list(dict.fromkeys(self.nodes['Genesymbol'].tolist()))node_set=set(node_labels)edge_nodes=set(interactions['source']).union(interactions['target'])missing_nodes=edge_nodes.difference(node_set)ifmissing_nodes:detail=', '.join(sorted(map(str,missing_nodes)))raiseValueError('BNet interaction endpoints are absent from the node table: 'f'{detail}.',)# Identify undefined interactionsundefined_interactions=interactions.query("Effect == 'undefined'")ifnotundefined_interactions.empty:print(f"Warning: The network has {len(undefined_interactions)} UNDEFINED interaction(s).")print("Undefined interactions:")for_,rowinundefined_interactions.iterrows():print(f"{row['source']} -> {row['target']}")print(f"Reference: {row['References']}")# Identify bimodal interactionsbimodal_interactions=interactions.query("Effect == 'bimodal'")ifnotbimodal_interactions.empty:print(f"Warning: The network has {len(bimodal_interactions)} BIMODAL interaction(s).")print("Bimodal interactions:")for_,rowinbimodal_interactions.iterrows():print(f"{row['source']} -> {row['target']}")print(f"Reference: {row['References']}")ifnisnotNoneand(notisinstance(n,int)orisinstance(n,bool)orn<0):raiseValueError('n must be a non-negative integer or None.')# Keep this iterator lazy: materializing all 2^k variants defeats the# purpose of `n` and can exhaust memory before the first file is made.permutations=itertools.product(['stimulation','inhibition'],repeat=len(bimodal_interactions),)ifnisnotNone:permutations=itertools.islice(permutations,n)bimodal_indices=bimodal_interactions.index.tolist()sanitized_nodes={node:_sanitize_bnet_identifier(node)fornodeinnode_labels}collisions={}fororiginal,sanitizedinsanitized_nodes.items():collisions.setdefault(sanitized,[]).append(original)collisions={sanitized:originalsforsanitized,originalsincollisions.items()iflen(originals)>1}ifcollisions:detail='; '.join(f'{sanitized}: {", ".join(map(str,originals))}'forsanitized,originalsincollisions.items())raiseValueError('Node labels collide after BNet identifier sanitization 'f'({detail}).',)# Create a directory for the BNet files if a directory is provideddirectory=os.path.dirname(file_name)ifdirectory:os.makedirs(directory,exist_ok=True)# A single variant keeps the self-contained schema 1 companion that# callers such as the MCP server read. Several variants share one# history file instead of each embedding an identical copy.variant_count=2**len(bimodal_indices)ifnisnotNone:variant_count=min(n,variant_count)history_reference=Noneifvariant_count>1andgetattr(self,'_history',None)isnotNone:history_reference=self._write_shared_history(f"{os.path.splitext(file_name)[0]}.bnet.history.json")# Iterate through permutations and create a BNet file for eachgenerated=0fori,perminenumerate(permutations):# Create a copy of the interactions DataFrameinteractions_copy=interactions.copy()# Update bimodal interactions based on the current permutationforinteraction_index,effectinzip(bimodal_indices,perm):interactions_copy.loc[interaction_index,'Effect']=effect# Pre-filter stimulations, inhibitions, and exclude undefined effectsstimulations=interactions_copy.query("Effect == 'stimulation'")inhibitions=interactions_copy.query("Effect == 'inhibition'")complex_formation=interactions_copy.query("Effect == 'form complex'")# Generate the file name for this permutationperm_file_name=f"{os.path.splitext(file_name)[0]}_{i+1}.bnet"withopen(perm_file_name,"w")asf:f.write("# model in BoolNet format\n")f.write("targets, factors\n")fornodeinnode_labels:sanitized_node=sanitized_nodes[node]formula_on=[_sanitize_bnet_identifier(src)forsrcinstimulations.loc[stimulations['target']==node,'source',].tolist()]formula_off=[_sanitize_bnet_identifier(src)forsrcininhibitions.loc[inhibitions['target']==node,'source',].tolist()]formula_complex=[_sanitize_bnet_identifier(src)forsrcincomplex_formation.loc[complex_formation['target']==node,'source',].tolist()]# Constructing the formulaformula_parts=[]ifformula_complex:formula_parts.append(f"({' & '.join(formula_complex)})")ifformula_on:formula_parts.append(f"({' | '.join(formula_on)})")ifformula_off:formula_parts.append("!({})".format(" | ".join(formula_off)))# Writing the node and its formula to the fileformula=' & '.join(formula_parts)ifformula_partselsesanitized_nodef.write(f"{sanitized_node}, {formula}\n")print(f"Created BNet file: {perm_file_name}")self._write_companion(perm_file_name,'bnet',interactions_copy,sanitized_nodes,history_reference)generated+=1print(f"Generated {generated} BNet files.")
defexport_sif(self,file_name="logic_model.sif"):""" Function to export the network in SIF format """directory=os.path.dirname(file_name)ifdirectory:os.makedirs(directory,exist_ok=True)withopen(file_name,'w')asfile:for_,rowinself.interactions.iterrows():# Use the Effect column directly assuming it contains "activate" or "inhibit"interaction_type=row['Effect']ifinteraction_type=="form complex":interaction_type="form_complex"# Reference for the interactioninteraction_reference=row['References']# Adjust column name if necessary# Write a comment line with the interaction referencefile.write(f"# Reference PMID: {interaction_reference}\n")ifisinstance(row.get('Provenance'),dict):file.write('# NeKo provenance: '+json.dumps(row['Provenance'],allow_nan=False)+'\n')# Write the formatted interaction to the .sif filefile.write(f"{row['source']}\t{interaction_type}\t{row['target']}\n")self._write_companion(file_name,'sif',self.interactions,{})return