Source code for rkviewer.iodine

"""
Iodine Network Object Model.

Original author:    RJ Zhou
"Forked" from:      https://github.com/zrj26/go-NOM
Adapted by:         Gary Geng

TODOs
    * Phase out errCode, or at least provide more detalis in error messages.
"""
# from __future__ import annotations
import abc
from re import S, X
from functools import partial

from marshmallow.decorators import post_dump, post_load, pre_load
from marshmallow_polyfield import PolyField
from numpy.lib.npyio import recfromcsv
from .mvc import (ModifierTipStyle, IDNotFoundError, IDRepeatError, NodeNotFreeError, NetIndexError,
                  ReactionIndexError, NodeIndexError, CompartmentIndexError, StoichError,
                  StackEmptyError, JSONError, FileError)
from .config import ColorField, Pixel, Dim, Dim2, Color, Font, FontField, get_theme
from .canvas.geometry import Vec2
from .canvas.data import CompositeShapeFactory, PrimitiveFactory, CirclePrim, CompositeShape, Primitive, \
    RectanglePrim, HexagonPrim, LinePrim, TrianglePrim, Transform, TextPrim, ChoiceItem,\
    FONT_FAMILY_CHOICES, FONT_STYLE_CHOICES, FONT_WEIGHT_CHOICES, TEXT_ALIGNMENT_CHOICES
import copy
from dataclasses import dataclass, field
import json
from typing import Any, DefaultDict, Dict, MutableSet, Optional, Set, Tuple, List, cast
from enum import Enum
from collections import defaultdict
from marshmallow import Schema, fields, validate, missing as missing_, ValidationError, pre_dump
from pprint import pprint


# The current version of the network serialization schema.
SERIAL_VERSION = "1.0.0"


[docs]def get_theme_fn(name): return partial(get_theme, name, convert_color=False)
# kwargs common to geometric shapes geometry_kwargs = { 'fill_color': get_theme_fn('node_fill'), 'border_color': get_theme_fn('node_border'), 'border_width': get_theme_fn('node_border_width'), } # Default primitive factories rectFact = PrimitiveFactory(RectanglePrim, **geometry_kwargs) circleFact = PrimitiveFactory(CirclePrim, **geometry_kwargs) hexFact = PrimitiveFactory(HexagonPrim, **geometry_kwargs) lineFact = PrimitiveFactory(LinePrim, **geometry_kwargs) triangleFact = PrimitiveFactory(TrianglePrim, **geometry_kwargs) textFact = PrimitiveFactory(TextPrim) singletonTrans = Transform() # fills the entire bounding box DEFAULT_SHAPE_FACTORY = CompositeShapeFactory([(rectFact, singletonTrans)], (textFact, singletonTrans), 'rectangle') # These are the default shape factories. They should never be modified by the user. shapeFactories = [ DEFAULT_SHAPE_FACTORY, CompositeShapeFactory([(circleFact, singletonTrans)], (textFact, singletonTrans), 'circle'), CompositeShapeFactory([(hexFact, singletonTrans)], (textFact, singletonTrans), 'hexagon'), CompositeShapeFactory([(lineFact, singletonTrans)], (textFact, singletonTrans), 'line'), CompositeShapeFactory([(triangleFact, singletonTrans)], (textFact, singletonTrans), 'triangle'), CompositeShapeFactory([], (textFact, singletonTrans), 'text-only'), CompositeShapeFactory([(circleFact, singletonTrans)], (textFact, Transform(translation=Vec2(1, 1))), 'text outside'), CompositeShapeFactory([(circleFact, Transform(scale=Vec2.repeat(0.5))), (circleFact, Transform(scale=Vec2.repeat(0.5), translation=Vec2.repeat(0.5))), (PrimitiveFactory(RectanglePrim, fill_color=Color(255, 0, 0, 255)), Transform(scale=Vec2.repeat(0.5), translation=Vec2.repeat(0.25))) ], (PrimitiveFactory(TextPrim, font_color=Color(255, 255, 255, 255)), singletonTrans), 'demo combo'), ] # TODO add lockedNode here too
[docs]class TAbstractNode(abc.ABC): index: int position: Vec2 rectSize: Vec2 compi: int nodeLocked: bool
[docs]@dataclass class TNode(TAbstractNode): index: int id: str position: Vec2 rectSize: Vec2 floating : bool # If false it means the node is a boundary node nodeLocked: bool #if false it means the node can be moved compi: int = -1 shapei: int = 0 shape: CompositeShape = field(default_factory=lambda: shapeFactories[0].produce())
[docs]@dataclass class TAliasNode(TAbstractNode): index: int position: Vec2 rectSize: Vec2 originalIdx: int nodeLocked: bool compi: int = -1
[docs]class TNetwork: '''Represents an entire reaction network. **NOTE IMPORTANT** whenever any change is made to the code that changes how the network is serialized/deserialized, one must bump the global variable SERIAL_VERSION to reflect that. See NetworkSchema::serialVersion for more information. ''' id: str nodes: Dict[int, TAbstractNode] reactions: Dict[int, 'TReaction'] compartments: Dict[int, 'TCompartment'] baseNodes: Set[int] #: Set of node indices not in any compartment srcMap: DefaultDict[int, MutableSet[int]] #: Map nodes to reactions of which it is a source destMap: DefaultDict[int, MutableSet[int]] #: Map nodes to reactions of which it is a target lastNodeIdx: int lastReactionIdx: int lastCompartmentIdx: int def __init__(self, id: str, nodes: Dict[int, TAbstractNode] = None, reactions: Dict[int, 'TReaction'] = None, compartments: Dict[int, 'TCompartment'] = None, ): if nodes is None: nodes = dict() if reactions is None: reactions = dict() if compartments is None: compartments = dict() self.id = id self.nodes = nodes self.reactions = reactions self.compartments = compartments self.baseNodes = set(index for index, n in nodes.items() if n.compi == -1) self.srcMap = defaultdict(set) self.destMap = defaultdict(set) # Initialize srcMap and destMap for index, reaction in reactions.items(): for src in reaction.reactants: self.srcMap[src].add(index) for dest in reaction.products: self.destMap[dest].add(index) self.lastNodeIdx = max(nodes.keys(), default=-1) + 1 self.lastReactionIdx = max(reactions.keys(), default=-1) + 1 self.lastCompartmentIdx = max(compartments.keys(), default=-1) + 1
[docs] def addNode(self, node: TAbstractNode) -> int: self.nodes[self.lastNodeIdx] = node self.baseNodes.add(self.lastNodeIdx) ret = self.lastNodeIdx self.lastNodeIdx += 1 return ret
[docs] def addReaction(self, rea: 'TReaction'): self.reactions[self.lastReactionIdx] = rea # update nodeToReactions for src in rea.reactants: self.srcMap[src].add(self.lastReactionIdx) for dest in rea.products: self.destMap[dest].add(self.lastReactionIdx) self.lastReactionIdx += 1
[docs] def addCompartment(self, comp: 'TCompartment') -> int: ind = self.lastCompartmentIdx self.compartments[ind] = comp self.lastCompartmentIdx += 1 return ind
[docs]@dataclass class TReaction: id: str centerPos: Optional[Vec2] = None rateLaw: str = "" reactants: Dict[int, 'TSpeciesNode'] = field(default_factory=dict) products: Dict[int, 'TSpeciesNode'] = field(default_factory=dict) fillColor: Color = Color(255, 150, 80, 255) thickness: float = 3.0 centerHandlePos: Vec2 = Vec2() bezierCurves: bool = True # If false it means a straight line modifiers: Set[int] = field(default_factory=set) tipStyle: ModifierTipStyle = ModifierTipStyle.CIRCLE
[docs]@dataclass class TSpeciesNode: stoich: float handlePos: Vec2 = Vec2()
[docs]@dataclass class TCompartment: id: str position: Vec2 rectSize: Vec2 node_indices: Set[int] = field(default_factory=set) volume: float = 1 fillColor: Color = Color(0, 247, 255, 255) outlineColor: Color = Color(0, 106, 255, 255) outlineThickness: float = 2
[docs]class TStack: items: List['TNetworkDict'] def __init__(self): self.items = []
[docs] def isEmpty(self): return self.items == []
[docs] def push(self, netDict: 'TNetworkDict'): theSet = copy.deepcopy(netDict) self.items.append(theSet)
[docs] def pop(self): return self.items.pop()
[docs]class TNetworkDict(Dict[int, TNetwork]): def __init__(self): super().__init__() self.lastNetIndex = 0
[docs]class ErrorCode(Enum): OK = 0 OTHER = -1 ID_NOT_FOUND = -2 ID_REPEAT = -3 NODE_NOT_FREE = -4 NETI_NOT_FOUND = -5 REAI_NOT_FOUND = -6 NODEI_NOT_FOUND = -7 BAD_STOICH = -8 STACK_EMPTY = -9 JSON_ERROR = -10 FILE_ERROR = -11 OUT_OF_RANGE = -12 COMPI_NOT_FOUND = -13
errorDict = { 0: "ok", -1: "other", -2: "id not found", -3: "id repeat", -4: "node is not free", -5: "net index not found", -6: "reaction index not found", -7: "node index not found", -8: "bad stoich: stoich has to be positive", -9: "undo/redo stack is empty", -10: "Json convert error", -11: "File error", -12: "Variable out of range", -13: "Compartment index not found", } ExceptionDict = { -2: IDNotFoundError, -3: IDRepeatError, -4: NodeNotFreeError, -5: NetIndexError, -6: ReactionIndexError, -7: NodeIndexError, -8: StoichError, -9: StackEmptyError, -10: JSONError, -11: FileError, -12: ValueError, -13: CompartmentIndexError, } fontFamilyDict = { "default": 0, "decorative": 1, "roman": 2, "script": 3, "swiss": 4, "modern": 5, } fontStyleDict = { "normal": 0, "italic": 1, } fontWeightDict = { "default": 0, "light": 1, "bold": 2, } stackFlag: bool = True errCode: int = 0 # dict mapping from index to network networkDict: TNetworkDict = TNetworkDict() undoStack: TStack = TStack() redoStack: TStack = TStack() lastNetIndex: int = 0
[docs]def getErrorCode(): """get the error code of last function""" global errCode return errCode
[docs]def undo(): """ Undo ge back to last state errCode: -9: stack is empty """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if undoStack.isEmpty(): errCode = -9 else: redoStack.push(networkDict) networkDict = undoStack.pop() if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode])
[docs]def redo(): """ Redo redo errCode: -9: stack is empty """ global stackFlag, errCode, networkDict, undoStack, redoStack if redoStack.isEmpty(): errCode = -9 else: undoStack.push(networkDict) networkDict = redoStack.pop() if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode])
[docs]def startGroup(): """ StartGroup used at the start of a group operaction or secondary function. """ global stackFlag, errCode, networkDict, undoStack, redoStack redoStack = TStack() undoStack.push(networkDict) stackFlag = False
[docs]def endGroup(): """ EndGroup used at the end of a group operaction or secondary function. """ global stackFlag stackFlag = True
[docs]def newNetwork(netID: str): """ newNetwork Create a new network errCode -3: id repeat, 0 :ok """ global stackFlag, errCode, networkDict, undoStack, redoStack, lastNetIndex errCode = 0 for network in networkDict.values(): if network.id == netID: errCode = -3 break if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: _pushUndoStack() newNetwork = TNetwork(netID) networkDict[lastNetIndex] = newNetwork lastNetIndex += 1
[docs]def getNetworkIndex(netID: str) -> int: """ getNetworkIndex return: -2: net id can't find """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = -2 for i, net in networkDict.items(): if net.id == netID: errCode = 0 return i raise ExceptionDict[errCode](errorDict[errCode])
# def saveNetworkAsJSON(neti: int, fileName: str): # """ # SaveNetworkAsJSON SaveNetworkAsJSON # errCode: -5: net index out of range # -10: "Json convert error", -11: "File error" # """ # global stackFlag, errCode, networkDict, undoStack, redoStack # errCode = 0 # if neti not in networkDict: # errCode = -5 # raise ExceptionDict[errCode](errorDict[errCode]) # else: # data2 = json.dumps(networkDict[neti], # sort_keys=True, indent=4, separators=(',', ': ')) # print(data2) # #ReadNetworkFromJSON ReadNetworkFromJSON # #errCode -3: id repeat, 0 :ok # #-10: "Json convert error", -11: "File error", # def ReadNetworkFromJSON(filePath string) int : # errCode = 0 # file, err1 = ioutil.ReadFile(filePath) # if err1 != nil : # errCode = -11 # addErrorMessage(errCode, "(\"" + filePath + "\")", "", "") # return errCode # newNet = TNetwork{ # err2 = json.Unmarshal([]byte(file), &newNet) # if err2 != nil : # errCode = -10 # addErrorMessage(errCode, "(\"" + filePath + "\")", "", "") # return errCode # for i = range networkDict : # if newNet.id == networkDict[i].id : # errCode = -3 # addErrorMessage(errCode, ("(\"" + filePath + "\")"), newNet.id, "") # return errCode # if stackFlag : # redoStack = TundoStack{ # undoStack.push(networkDict) # networkDict = append(networkDict, newNet) # # fmt.Println(networkDict) # return errCode
[docs]def deleteNetwork(neti: int): """ DeleteNetwork DeleteNetwork errCode: -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: _pushUndoStack() del networkDict[neti]
[docs]def clearNetworks(): global stackFlag, errCode, networkDict, undoStack, redoStack, lastNetIndex errCode = 0 _pushUndoStack() networkDict = TNetworkDict() lastNetIndex = 0
[docs]def getNumberOfNetworks(): return len(networkDict)
[docs]def getNetworkID(neti: int): """ GetNetworkID GetID of network errCode: -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: return networkDict[neti].id
[docs]def getListOfNetworks() -> List[int]: return list(networkDict.keys())
def _raiseError(eCode: int): global errCode assert eCode < 0 errCode = eCode raise ExceptionDict[errCode](errorDict[errCode]) def _addNetwork(network: TNetwork) -> int: """Helper function that adds a network object.""" global lastNetIndex for net in networkDict.values(): if net.id == network.id: _raiseError(-3) _pushUndoStack() networkDict[lastNetIndex] = network lastNetIndex += 1 return lastNetIndex - 1 def _getNetwork(neti: int) -> TNetwork: if neti not in networkDict: errCode = -5 raise ExceptionDict[errCode](errorDict[errCode]) return networkDict[neti] def _getNodeOrAlias(neti: int, nodei: int) -> TAbstractNode: net = _getNetwork(neti) if nodei not in net.nodes: _raiseError(-7) return net.nodes[nodei] def _getConcreteNode(neti: int, nodei: int) -> TNode: net = _getNetwork(neti) node = _getNodeOrAlias(neti, nodei) if isinstance(node, TAliasNode): # get the original node originalIdx = node.originalIdx assert originalIdx in net.nodes node = net.nodes[originalIdx] assert isinstance(node, TNode) return node def _getReaction(neti: int, reai: int) -> TReaction: net = _getNetwork(neti) if reai not in net.reactions: _raiseError(-6) return net.reactions[reai] def _getCompartment(neti: int, compi: int) -> TCompartment: global errCode net = _getNetwork(neti) if compi not in net.compartments: errCode = -13 raise CompartmentIndexError('Unknown index: {}'.format(compi)) return net.compartments[compi] def _pushUndoStack(): global stackFlag, errCode, networkDict, undoStack, redoStack if stackFlag: redoStack = TStack() undoStack.push(networkDict)
[docs]def addNode(neti: int, nodeID: str, x: float, y: float, w: float, h: float, floatingNode: bool = True, nodeLocked: bool = False) -> int: """ AddNode adds a node to the network errCode - 3: id repeat, 0: ok -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 n = _getNetwork(neti) for node in n.nodes.values(): if isinstance(node, TNode) and node.id == nodeID: _raiseError(-3) if x < 0 or y < 0 or w <= 0 or h <= 0: _raiseError(-12) _pushUndoStack() newNode = TNode(n.lastNodeIdx, nodeID, Vec2(x, y), Vec2(w, h), floatingNode, nodeLocked) return n.addNode(newNode)
[docs]def addAliasNode(neti: int, originalIdx: int, x: float, y: float, w: float, h: float) -> int: net = _getNetwork(neti) # make sure we can get it original_node = _getConcreteNode(neti, originalIdx) _pushUndoStack() # Refer to the original node's index, whether 'original_index' is a TNode or a TAliasNode anode = TAliasNode(net.lastNodeIdx, Vec2(x, y), Vec2(w, h), original_node.index, nodeLocked=False) anodei = net.addNode(anode) setCompartmentOfNode(neti, anodei, original_node.compi) return anodei
[docs]def aliasForReaction(neti: int, reai: int, nodei: int, x: float, y: float, w: float, h: float): '''Create an alias for nodei, and replace each instance of nodei in reai with the alias ''' # ensure that the original node exists _getConcreteNode(neti, nodei) _pushUndoStack() aliasi = addAliasNode(neti, nodei, x, y, w, h) reaction = _getReaction(neti, reai) net = _getNetwork(neti) # update reactants and srcMap if nodei in reaction.reactants: reaction.reactants[aliasi] = reaction.reactants[nodei] del reaction.reactants[nodei] net.srcMap[nodei].remove(reai) net.srcMap[aliasi].add(reai) # update products and destMap if nodei in reaction.products: reaction.products[aliasi] = reaction.products[nodei] del reaction.products[nodei] net.destMap[nodei].remove(reai) net.destMap[aliasi].add(reai)
[docs]def getNodeIndex(neti: int, nodeID: str): """ GetNodeIndex get node index by id errCode: -2: node id not found, -5: net index out of range return: >=0 """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = -2 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] for i, node, in n.nodes.items(): if isinstance(node, TNode) and node.id == nodeID: errCode = 0 return i assert errCode < 0 raise ExceptionDict[errCode](errorDict[errCode])
[docs]def deleteNode(neti: int, nodei: int) -> bool: """ DeleteNode deletes the node with index. Returns whether there was a node with the given index, i.e. whether a node was deleted. This does not throw an error due to the possibility of someone deleting nodes in a loop, in which case an original copy may be deleted before its alias, and so when the alias is reached, it no longer exists. """ def deleteHelper(net: TNetwork, node: TAbstractNode, neti: int, nodei: int, is_alias: bool): # to delete an alias, remove it from the compartment # modify the reactions that it is in, so that previous references now point to the original # node. Also modify the modifiers to do the same if is_alias: # swap all occurrences of alias with the original node, since we're deleting the alias assert isinstance(node, TAliasNode) srcReactions = net.srcMap[nodei] destReactions = net.destMap[nodei] # put the original node in the reaction in the place of the alias node for reai in srcReactions: rxn = net.reactions[reai] rxn.reactants[node.originalIdx] = rxn.reactants[nodei] # I'm not sure what should happen if both a node and its alias are reactants of # the same reaction. Originally I thought of adding up the stoich of the deleted # alias to that of the original node, but frankly this is such a nonsensical case # that I think doing so would be making it unncessarily complicated. # So now I'm just deleting it and doing nothing # if original_species: # new_species = rxn.reactants[node.originalIdx] # new_species.stoich += original_species.stoich # new_species.handlePos = original_species.handlePos del rxn.reactants[nodei] for reai in destReactions: rxn = net.reactions[reai] # see above for explanation rxn.products[node.originalIdx] = rxn.products[nodei] # if original_species: # new_species = rxn.products[node.originalIdx] # new_species.stoich += original_species.stoich # new_species.handlePos = original_species.handlePos del rxn.products[nodei] # update srcMap and destMap net.srcMap[node.originalIdx] |= net.srcMap[nodei] net.destMap[node.originalIdx] |= net.destMap[nodei] del net.srcMap[nodei] del net.destMap[nodei] # replace occurrences in modifiers for rxn in net.reactions.values(): if nodei in rxn.modifiers: rxn.modifiers.remove(nodei) rxn.modifiers.add(node.originalIdx) else: # for now, disallow removing concrete nodes that are part of a reaction if len(net.srcMap[nodei]) != 0 or len(net.destMap[nodei]) != 0: _raiseError(-4) # remove self from modifiers list for rxn in net.reactions.values(): rxn.modifiers.discard(nodei) # remove from compartment compi = getCompartmentOfNode(neti, nodei) if compi == -1: net.baseNodes.remove(nodei) else: net.compartments[compi].node_indices.remove(nodei) # remove from 'nodes' del net.nodes[nodei] net = _getNetwork(neti) try: node = _getNodeOrAlias(neti, nodei) except NodeIndexError: return False # validate that node is not part of a reaction if isinstance(node, TNode) and (len(net.srcMap[nodei]) != 0 or len(net.destMap[nodei]) != 0): _raiseError(-4) _pushUndoStack() is_alias = isinstance(node, TAliasNode) if is_alias: deleteHelper(net, node, neti, nodei, True) else: # delete all the aliases of node if node is an original node alias_indices = [i for i, n in net.nodes.items() if isinstance(n, TAliasNode) and cast(TAliasNode, n).originalIdx == nodei] for alias_idx in alias_indices: deleteHelper(net, net.nodes[alias_idx], neti, alias_idx, True) # delete original node deleteHelper(net, node, neti, nodei, False) return True
[docs]def clearNetwork(neti: int): """ ClearNetwork clear all nodes and reactions in this network errCode: -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: _pushUndoStack() # networkDict[neti].nodes.clear() # networkDict[neti].reactions.clear() # networkDict[neti].compartments.clear() networkDict[neti] = TNetwork(networkDict[neti].id)
[docs]def getNumberOfNodes(neti: int): """ GetNumberOfNodes get the number of nodes in the current network num: >= -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: n = networkDict[neti] return len(n.nodes)
[docs]def getNodeCenter(neti: int, nodei: int): """ GetNodeCenter Get the X and Y coordinate of the Center of node errCode: -7: node index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] if nodei not in n.nodes: errCode = -7 else: X = round(n.nodes[nodei].position.x + n.nodes[nodei].rectSize.x*0.5, 2) Y = round(n.nodes[nodei].position.y + n.nodes[nodei].rectSize.y*0.5, 2) return (X, Y) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getNodeID(neti: int, nodei: int): """ GetNodeID Get the id of the node errCode: -7: node index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: node = _getConcreteNode(neti, nodei) return node.id raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getOriginalIndex(neti: int, nodei: int) -> int: '''Return -1 if the node is an original, or if this is an alias, return the original index.''' node = _getNodeOrAlias(neti, nodei) if isinstance(node, TNode): return -1 else: assert isinstance(node, TAliasNode) return node.originalIdx
[docs]def IsFloatingNode(neti : int, nodei : int): return _getConcreteNode(neti, nodei).floating
[docs]def IsBoundaryNode(neti : int, nodei : int): return not IsFloatingNode(neti, nodei)
[docs]def IsNodeLocked(neti: int, nodei: int): return _getNodeOrAlias(neti, nodei).nodeLocked
[docs]def getListOfNodeIDs(neti: int) -> List[str]: if neti not in networkDict: errCode = -5 raise ExceptionDict[errCode](errorDict[errCode]) return [n.id for n in networkDict[neti].nodes.values() if isinstance(n, TNode)]
[docs]def getListOfNodeIndices(neti: int) -> Set[int]: return cast(Set[int], _getNetwork(neti).nodes.keys())
[docs]def getListOfReactionIndices(neti: int) -> Set[int]: return cast(Set[int], _getNetwork(neti).reactions.keys())
[docs]def getListOfCompartmentIndices(neti: int) -> Set[int]: return cast(Set[int], _getNetwork(neti).compartments.keys())
[docs]def getSrcReactions(neti: int, nodei: int) -> Set[int]: return set(_getNetwork(neti).srcMap[nodei])
[docs]def getDestReactions(neti: int, nodei: int) -> Set[int]: return set(_getNetwork(neti).destMap[nodei])
[docs]def getNodeCoordinateAndSize(neti: int, nodei: int): """ getNodeCoordinateAndSize get the x,y,w,h of the node errCode:-7: node index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] if nodei not in n.nodes: errCode = -7 else: X = round(n.nodes[nodei].position.x, 2) Y = round(n.nodes[nodei].position.y, 2) W = round(n.nodes[nodei].rectSize.x, 2) H = round(n.nodes[nodei].rectSize.y, 2) return (X, Y, W, H) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def colorToRGB(color: Color): color1 = color.r color1 = (color1 << 8) | color.g color1 = (color1 << 8) | color.b return color1
[docs]def getNodeFillColor(neti: int, nodei: int): """ Return the 'fill_color' property of the first primitive in the given node's composite shape, if there is such a primitive. Otherwise, return None. This function exists for backwards-compatibility and convenience reasons. errCode: -7: node index out of range -5: net index out of range """ node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'fill_color' in prim.__dataclass_fields__: ret = getattr(prim, 'fill_color') assert isinstance(ret, Color) return ret return None
[docs]def getNodeFillColorRGB(neti: int, nodei: int): """ See getNodeFillColor(), except only returns the RGB values errCode: -7: node index out of range -5: net index out of range """ color = getNodeFillColor(neti, nodei) if color is None: return None return colorToRGB(color)
[docs]def getNodeFillColorAlpha(neti: int, nodei: int): """ getNodeFillColorAlpha getNodeFillColor alpha value(float) errCode: -7: node index out of range -5: net index out of range """ color = getNodeFillColor(neti, nodei) if color is None: return None return float(color.a) / 255
[docs]def getNodeBorderColor(neti: int, nodei: int): """ getNodeBorderColor rgba tulple format, rgb range int[0,255] alpha range float[0,1] errCode: -7: node index out of range -5: net index out of range """ node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'border_color' in prim.__dataclass_fields__: ret = getattr(prim, 'border_color') assert isinstance(ret, Color) return ret return None
[docs]def getNodeBorderColorRGB(neti: int, nodei: int): """ getNodeBorderColorRGB getNodeBorderColor rgb int format errCode: -7: node index out of range -5: net index out of range """ color = getNodeBorderColor(neti, nodei) if color is None: return None return colorToRGB(color)
[docs]def getNodeBorderColorAlpha(neti: int, nodei: int): """ getNodeBorderColorAlpha getNodeBorderColor alpha value(float) errCode: -7: node index out of range -5: net index out of range """ color = getNodeBorderColor(neti, nodei) if color is None: return None return float(color.a) / 255
[docs]def getNodeBorderWidth(neti: int, nodei: int): """ getNodeBorderWidth errCode: -7: node index out of range -5: net index out of range """ node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'border_width' in prim.__dataclass_fields__: return getattr(prim, 'border_width') return None
[docs]def setNodeID(neti: int, nodei: int, newID: str): """ setNodeID set the id of a node errCode -3: id repeat -5: net index out of range -7: node index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: net = networkDict[neti] if nodei not in net.nodes.keys(): errCode = -7 else: if any((n.id == newID for n in net.nodes.values() if isinstance(n, TNode))): errCode = -3 else: _pushUndoStack() _getConcreteNode(neti, nodei).id = newID return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setNodeCoordinate(neti: int, nodei: int, x: float, y: float, allowNegativeCoordinates: bool = False): """ setNodeCoordinate setNodeCoordinate errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if allowNegativeCoordinates: lowerLimit = -1E12 else: lowerLimit = 0 if neti not in networkDict: errCode = -5 else: if x < lowerLimit or y < lowerLimit: _raiseError(-12) n = _getNodeOrAlias(neti, nodei) # only move if node is locked if not n.nodeLocked: _pushUndoStack() n.position = Vec2(x, y) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setNodeSize(neti: int, nodei: int, w: float, h: float): """ setNodeSize setNodeSize errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] if nodei not in n.nodes: errCode = -7 elif w <= 0 or h <= 0: errCode = -12 else: _pushUndoStack() n.nodes[nodei].rectSize = Vec2(w, h) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setNodeFloatingStatus (neti: int, nodei: int, floatingStatus : bool): """ setNodeFloatingStatus setNodeFloatingStatus errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] if nodei not in n.nodes: errCode = -7 else: _pushUndoStack() _getConcreteNode(neti, nodei).floating = floatingStatus return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setNodeLockedStatus (neti: int, nodei: int, lockedNode: bool): """ setNodeLockedStatus setNodeLockedStatus errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: n = networkDict[neti] if nodei not in n.nodes: errCode = -7 else: _pushUndoStack() _getNodeOrAlias(neti, nodei).nodeLocked = lockedNode return
[docs]def setNodeFillColorRGB(neti: int, nodei: int, r: int, g: int, b: int): """ setNodeFillColorRGB setNodeFillColorRGB errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255: _raiseError(-12) node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'fill_color' in prim.__dataclass_fields__: old_color = getattr(prim, 'fill_color') assert isinstance(old_color, Color) setattr(prim, 'fill_color', old_color.swapped(r, g, b))
[docs]def setNodeFillColorAlpha(neti: int, nodei: int, a: float): """ setNodeFillColorAlpha setNodeFillColorAlpha errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ if a < 0 or a > 1: _raiseError(-12) node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'fill_color' in prim.__dataclass_fields__: a_int = int(a * 255) old_color = getattr(prim, 'fill_color') assert isinstance(old_color, Color) setattr(prim, 'fill_color', old_color.swapped(a=a_int))
[docs]def setNodeBorderColorRGB(neti: int, nodei: int, r: int, g: int, b: int): """ setNodeBorderColorRGB setNodeBorderColorRGB errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255: _raiseError(-12) node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'border_color' in prim.__dataclass_fields__: old_color = getattr(prim, 'border_color') assert isinstance(old_color, Color) setattr(prim, 'border_color', old_color.swapped(r, g, b))
[docs]def setNodeBorderColorAlpha(neti: int, nodei: int, a: float): """ setNodeBorderColorAlpha setNodeBorderColorAlpha, alpha is a float between 0 and 1 errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ if a < 0 or a > 1: _raiseError(-12) node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'border_color' in prim.__dataclass_fields__: a_int = int(a * 255) old_color = getattr(prim, 'border_color') assert isinstance(old_color, Color) setattr(prim, 'border_color', old_color.swapped(a=a_int))
[docs]def setNodeBorderWidth(neti: int, nodei: int, width: float): """ setNodeBorderWidth setNodeBorderWidth errCode: -7: node index out of range -5: net index out of range -12: Variable out of range """ if width <= 0: _raiseError(-12) node = _getConcreteNode(neti, nodei) for prim, _ in node.shape.items: if 'border_width' in prim.__dataclass_fields__: setattr(prim, 'border_width', width)
[docs]def createReaction(neti: int, reaID: str, sources: List[int], targets: List[int]): """ createReaction create an empty reacton errCode: -3: id repeat -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if len(sources) == 0 or len(targets) == 0: raise ValueError("Reaction '{}' has no reactants or it has no products".format(reaID)) net = _getNetwork(neti) # duplicate ID? if any((r.id == reaID for r in net.reactions.values())): errCode = -3 else: # ensure nodes exist if any(nodei not in net.nodes.keys() for nodei in sources): _raiseError(-7) if any(nodei not in net.nodes.keys() for nodei in targets): _raiseError(-7) if set(sources) == set(targets): raise ValueError('Reaction source node set and target node set cannot be identical.') _pushUndoStack() newReact = TReaction(reaID) # Add src/target nodes for srcNodeIdx in sources: newReact.reactants[srcNodeIdx] = TSpeciesNode(1) # default stoich to 1 for destNodeIdx in targets: newReact.products[destNodeIdx] = TSpeciesNode(1) # default stoich to 1 net.addReaction(newReact) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionIndex(neti: int, reaID: str): """ getReactionIndex get reaction index by id return: -2: id can't find, >=0: ok -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: errCode = -2 for i, r in networkDict[neti].reactions.items(): if r.id == reaID: errCode = 0 return i raise ExceptionDict[errCode](errorDict[errCode])
[docs]def deleteReaction(neti: int, reai: int): """ deleteReaction delete the reaction with index errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: if reai not in networkDict[neti].reactions: errCode = -6 else: _pushUndoStack() net = _getNetwork(neti) reaction = _getReaction(neti, reai) for src in reaction.reactants: net.srcMap[src].remove(reai) for dest in reaction.products: net.destMap[dest].remove(reai) del networkDict[neti].reactions[reai] return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def clearReactions(neti: int): """ clearReactions clear all reactions in this network errCode: -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: _pushUndoStack() net = _getNetwork(neti) net.reactions.clear() net.srcMap.clear() net.destMap.clear()
[docs]def getNumberOfReactions(neti: int): """ getNumberOfReactions get the number of reactions in the current Reactionset return: >=0: ok, -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack if neti not in networkDict: errCode = -5 if errCode < 0: raise ExceptionDict[errCode](errorDict[errCode]) else: r = networkDict[neti].reactions return len(r)
[docs]def getReactionID(neti: int, reai: int): """ getReactionID get the id of Reaction errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return r[reai].id raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getListOfReactionIDs(neti: int) -> List[str]: if neti not in networkDict: errCode = -5 raise ExceptionDict[errCode](errorDict[errCode]) return [r.id for r in networkDict[neti].reactions.values()]
[docs]def getReactionRateLaw(neti: int, reai: int): """ getReactionRateLaw get the ratelaw of Reaction errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return r[reai].rateLaw raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionCenterPos(neti: int, reai: int): """ getReactionCenterPos get the center position of the Reaction """ r = _getReaction(neti, reai) return r.centerPos
[docs]def getReactionFillColor(neti: int, reai: int): """ getReactionFillColor rgba tulple format, rgb range int[0,255] alpha range float[0,1] errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return (r[reai].fillColor.r, r[reai].fillColor.g, r[reai].fillColor.b, float(r[reai].fillColor.a)/255) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionFillColorRGB(neti: int, reai: int): """ getReactionFillColorRGB getReactionFillColorRGB errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: color1 = r[reai].fillColor.r color1 = (color1 << 8) | r[reai].fillColor.g color1 = (color1 << 8) | r[reai].fillColor.b return color1 raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionFillColorAlpha(neti: int, reai: int): """ getReactionFillColorAlpha getReactionFillColorAlpha errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: alpha1 = float(r[reai].fillColor.a) / 255 return alpha1 raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionLineThickness(neti: int, reai: int): """ getReactionLineThickness getReactionLineThickness errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return r[reai].thickness raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionCenterHandlePosition(neti: int, reai: int): """ getReactionCenterHandlePosition getReactionCenterHandlePosition errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return (round(r[reai].centerHandlePos.x, 2), round(r[reai].centerHandlePos.y, 2)) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionSrcNodeStoich(neti: int, reai: int, srcNodeIdx: int): """ getReactionSrcNodeStoich get the SrcNode stoichiometry of Reaction errCode: -6: reaction index out of range, -5: net index out of range, -7: node index not found """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif srcNodeIdx not in networkDict[neti].nodes: errCode = -7 elif srcNodeIdx not in r[reai].reactants: raise ValueError('The given node index "{}" is not a reactant node of "{}"'.format( srcNodeIdx, reai)) else: return r[reai].reactants[srcNodeIdx].stoich raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionDestNodeStoich(neti: int, reai: int, destNodeIdx: int): """ getReactionDestNodeStoich get the DestNode stoichiometry of Reaction return: positive float : ok, -6: reaction index out of range, -7: node index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif destNodeIdx not in networkDict[neti].nodes: errCode = -7 elif destNodeIdx not in r[reai].products: raise ValueError('The given node index "{}" is not a product node of "{}"'.format( destNodeIdx, reai)) else: s = r[reai].products[destNodeIdx] return s.stoich raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionSrcNodeHandlePosition(neti: int, reai: int, srcNodeIdx: int): """ getReactionSrcNodeHandlePosition get the SrcNode HandlePosition of Reaction errCode: -6: reaction index out of range, -5: net index out of range, -7: node index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif srcNodeIdx not in networkDict[neti].nodes: errCode = -7 elif srcNodeIdx not in r[reai].reactants: raise ValueError('The given node index "{}" is not a reactant node of "{}"'.format( srcNodeIdx, reai)) else: return (round(r[reai].reactants[srcNodeIdx].handlePos.x, 2), round(r[reai].reactants[srcNodeIdx].handlePos.y, 2)) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getReactionDestNodeHandlePosition(neti: int, reai: int, destNodeIdx: int): """ getReactionDestNodeStoich get the DestNode HandlePosition of Reaction return: positive float : ok, -6: reaction index out of range, -7: node index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif destNodeIdx not in networkDict[neti].nodes: errCode = -7 elif destNodeIdx not in r[reai].products: raise ValueError('The given node index "{}" is not a product node of "{}"'.format( destNodeIdx, reai)) else: return (round(r[reai].products[destNodeIdx].handlePos.x, 2), round(r[reai].products[destNodeIdx].handlePos.y, 2)) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getNumberOfSrcNodes(neti: int, reai: int): """ getNumberOfSrcNodes get the SrcNode length of Reaction return: non-negative int: ok, -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return len(r[reai].reactants) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getNumberOfDestNodes(neti: int, reai: int): """ getNumberOfDestNodes get the DestNode length of Reaction return: non-negative int: ok, -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: return len(r[reai].products) raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getListOfReactionSrcNodes(neti: int, reai: int) -> List[int]: """ getListOfReactionSrcNodes getListOfReactionSrcNodes in alphabetical order return: non-empty slice : ok, -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: reactions = networkDict[neti].reactions if reai not in reactions: errCode = -6 else: list1 = [] for k in reactions[reai].reactants: list1.append(k) list1.sort() return list1 raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getListOfReactionDestNodes(neti: int, reai: int) -> List[int]: """ getListOfReactionDestNodes getListOfReactionDestNodes in alphabetical order return: non-empty slice : ok, -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: list1 = [] for k in r[reai].products: list1.append(k) list1.sort() return list1 raise ExceptionDict[errCode](errorDict[errCode])
[docs]def getListOfReactionSrcStoich(neti: int, reai: int) -> List[float]: n = getListOfReactionSrcNodes(neti, reai) srcStoichList = [] for srcNodeID in n: srcStoichList.append(getReactionSrcNodeStoich(neti, reai, srcNodeID)) return srcStoichList
[docs]def getListOfReactionDestStoich(neti: int, reai: int) -> List[float]: n = getListOfReactionDestNodes(neti, reai) destStoichList = [] for destNodeID in n: destStoichList.append(getReactionDestNodeStoich(neti, reai, destNodeID)) return destStoichList
[docs]def printReactionInfo(neti: int, reai: int): print("id:", getReactionID(neti, reai)) print("rateLaw:", getReactionRateLaw(neti, reai)) print("SrcNodes:", getListOfReactionSrcNodes(neti, reai)) print("DestNodes:", getListOfReactionDestNodes(neti, reai)) print("SrcNodeStoichs:", getListOfReactionSrcStoich(neti, reai)) print("DestNodeStoichs:", getListOfReactionDestStoich(neti, reai))
# def deleteSrcNode(neti: int, reai: int, srcNodeIdx: int): # """ # deleteSrcNode delete src nodes by id(ID). # errCode: -6: reaction index out of range, # -5: net index out of range # -2: id not found # """ # global stackFlag, errCode, networkDict, undoStack, redoStack # errCode = 0 # if neti not in networkDict: # errCode = -5 # else: # r = networkDict[neti].reactions # if reai not in networkDict[neti].reactions: # errCode = -6 # else: # rea = r[reai] # if srcNodeIdx not in rea.reactants: # errCode = -2 # else: # _pushUndoStack() # del rea.reactants[srcNodeIdx] # networkDict[neti].reactions[reai] = rea # return # raise ExceptionDict[errCode](errorDict[errCode]) # def deleteDestNode(neti: int, reai: int, destNodeIdx: int): # """ # deleteDestNode delete all dest nodes by id # errCode: -6: reaction index out of range, # -5: net index out of range # -2: id not found # """ # global stackFlag, errCode, networkDict, undoStack, redoStack # errCode = 0 # if neti not in networkDict: # errCode = -5 # else: # r = networkDict[neti].reactions # if reai not in networkDict[neti].reactions: # errCode = -6 # else: # rea = r[reai] # if destNodeIdx not in rea.products: # errCode = -2 # else: # _pushUndoStack() # del rea.products[destNodeIdx] # return # raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionID(neti: int, reai: int, newID: str): """ setReactionID edit id of reaction errCode: 0:ok, -6: reaction index out of range -5: net index out of range -3: id repeat """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: reactions = networkDict[neti].reactions if reai not in reactions: errCode = -6 else: if any((r.id == newID for r in reactions.values())): errCode = -3 else: _pushUndoStack() networkDict[neti].reactions[reai].id = newID return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setRateLaw(neti: int, reai: int, rateLaw: str): """ setRateLaw edit rate law of reaction errCode: -6: reaction index out of range -5: net index out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: if reai not in networkDict[neti].reactions: errCode = -6 else: _pushUndoStack() networkDict[neti].reactions[reai].rateLaw = rateLaw return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionCenterPos(neti: int, reai: int, centerPos: Optional[Vec2]): """ setReactionCenterPos set the center position of the Reaction """ r = _getReaction(neti, reai) r.centerPos = centerPos
[docs]def setReactionSrcNodeStoich(neti: int, reai: int, srcNodeIdx: int, newStoich: float): """ setReactionSrcNodeStoich edit Stoich by Reaction srcNodeID errCode: -6: reaction index not found, -5: net index not found -7: node index not found, -8: wrong stoich raises ValueError if given node index not a dest node """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif srcNodeIdx not in networkDict[neti].nodes: errCode = -7 elif srcNodeIdx not in r[reai].reactants: raise ValueError('The given node index "{}" is not a reactant node of "{}"'.format( srcNodeIdx, reai)) elif newStoich <= 0.0: errCode = -8 else: _pushUndoStack() networkDict[neti].reactions[reai].reactants[srcNodeIdx].stoich = newStoich return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionDestNodeStoich(neti: int, reai: int, destNodeIdx: int, newStoich: float): """ setReactionDestNodeStoich edit Stoich by Reaction destNodeID errCode: -6: reaction index out of range, -5: net index out of range, -7: node index not found, -8: wrong stoich raises ValueError if given node index not a dest node """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif destNodeIdx not in networkDict[neti].nodes: errCode = -7 elif destNodeIdx not in r[reai].products: raise ValueError('The given node index "{}" is not a product node of "{}"'.format( destNodeIdx, reai)) elif newStoich <= 0.0: errCode = -8 else: _pushUndoStack() networkDict[neti].reactions[reai].products[destNodeIdx].stoich = newStoich return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionSrcNodeHandlePosition(neti: int, reai: int, srcNodeIdx: int, handlePosX: float, handlePosY: float): """ setReactionSrcNodeHandlePosition edit HandlePosition by Reaction srcNodeID errCode: -6: reaction index out of range, -5: net index out of range, -7: node index not found -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif srcNodeIdx not in networkDict[neti].nodes: _raiseError(-7) elif srcNodeIdx not in r[reai].reactants: raise ValueError('The given node index "{}" is not a reactant node of "{}"'.format( srcNodeIdx, reai)) else: _pushUndoStack() networkDict[neti].reactions[reai].reactants[srcNodeIdx].handlePos = Vec2( handlePosX, handlePosY) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionDestNodeHandlePosition(neti: int, reai: int, destNodeIdx: int, handlePosX: float, handlePosY: float): """ setReactionDestNodeHandlePosition edit HandlePosition by Reaction destNodeID errCode: -6: reaction index out of range, -5: net index out of range, -2: id not found -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif destNodeIdx not in networkDict[neti].nodes: _raiseError(-7) elif destNodeIdx not in r[reai].products: raise ValueError('The given node index "{}" is not a product node of "{}"'.format( destNodeIdx, reai)) else: _pushUndoStack() networkDict[neti].reactions[reai].products[destNodeIdx].handlePos = Vec2( handlePosX, handlePosY) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionFillColorRGB(neti: int, reai: int, R: int, G: int, B: int): """ setReactionFillColorRGB setReactionFillColorRGB errCode: -6: reaction index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif R < 0 or R > 255 or G < 0 or G > 255 or B < 0 or B > 255: errCode = -12 else: _pushUndoStack() r[reai].fillColor = r[reai].fillColor.swapped(R, G, B) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionFillColorAlpha(neti: int, reai: int, a: float): """ setReactionFillColorAlpha setReactionFillColorAlpha errCode: -6: reaction index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 elif a < 0 or a > 1: errCode = -12 else: _pushUndoStack() A1 = int(a * 255) r[reai].fillColor = r[reai].fillColor.swapped(a=A1) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionLineThickness(neti: int, reai: int, thickness: float): """ setReactionLineThickness setReactionLineThickness errCode: -6: reaction index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: if reai not in networkDict[neti].reactions: errCode = -6 elif thickness <= 0: errCode = -12 else: _pushUndoStack() networkDict[neti].reactions[reai].thickness = thickness return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def bezier_curves(neti: int, reai: int): n = _getNetwork(neti) return n.reactions[reai].bezierCurves
[docs]def setReactionBezierCurves(neti: int, reai: int, bezierCurves: bool): """ setReactionBezierCurves setReactionBezierCurves errCode: -6: reaction index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: r = networkDict[neti].reactions if reai not in networkDict[neti].reactions: errCode = -6 else: _pushUndoStack() r[reai].bezierCurves = bezierCurves return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def setReactionModifiers(neti: int, reai: int, modifiers: Set[int]): r = _getReaction(neti, reai) r.modifiers = copy.copy(set(modifiers))
[docs]def getReactionModifiers(neti: int, reai: int) -> Set[int]: r = _getReaction(neti, reai) return copy.copy(r.modifiers)
[docs]def setModifierTipStyle(neti: int, reai: int, tipStyle: ModifierTipStyle): r = _getReaction(neti, reai) r.tipStyle = tipStyle
[docs]def getModifierTipStyle(neti: int, reai: int) -> ModifierTipStyle: r = _getReaction(neti, reai) return r.tipStyle
[docs]def setReactionCenterHandlePosition(neti: int, reai: int, centerHandlePosX: float, centerHandlePosY: float): """ setReactionCenterHandlePosition setReactionCenterHandlePosition errCode: -6: reaction index out of range -5: net index out of range -12: Variable out of range """ global stackFlag, errCode, networkDict, undoStack, redoStack errCode = 0 if neti not in networkDict: errCode = -5 else: if reai not in networkDict[neti].reactions: errCode = -6 else: _pushUndoStack() networkDict[neti].reactions[reai].centerHandlePos = Vec2( centerHandlePosX, centerHandlePosY) return raise ExceptionDict[errCode](errorDict[errCode])
[docs]def addCompartment(neti: int, compID: str, x: float, y: float, w: float, h: float) -> int: """ Create a compartment and add to canvas. Return the index of the compartment added. Args: neti: network index. compID: ID of the compartment. x: x coordinate of top-left corner y: y coordinate of top-left corner w: width h: height """ if x < 0 or y < 0 or w < 0 or h < 0: _raiseError(-12) net = _getNetwork(neti) comp = TCompartment(compID, Vec2(x, y), Vec2(w, h)) if any((compID == c.id for c in net.compartments.values())): _raiseError(-3) _pushUndoStack() return net.addCompartment(comp)
[docs]def deleteCompartment(neti: int, compi: int): """Delete the compartment of the given index in the given network.""" net = _getNetwork(neti) if compi not in net.compartments: _raiseError(-13) _pushUndoStack() # Put all nodes in compartment in base compartment (-1) for nodei in net.compartments[compi].node_indices: assert net.nodes[nodei].compi == compi # move to base compartment net.nodes[nodei].compi = -1 net.baseNodes.add(nodei) del net.compartments[compi]
[docs]def getListOfCompartments(neti: int) -> List[int]: return list(_getNetwork(neti).compartments.keys())
[docs]def getNodesInCompartment(neti: int, compi: int) -> List[int]: """Return the list of node indices in the given compartment.""" if compi == -1: return list(_getNetwork(neti).baseNodes) return list(_getCompartment(neti, compi).node_indices) # Make copy in the process
[docs]def getCompartmentOfNode(neti: int, nodei: int) -> int: """Return the compartment index that the given node is in, or -1 if it is not in any.""" return _getNodeOrAlias(neti, nodei).compi
[docs]def setCompartmentOfNode(neti: int, nodei: int, compi: int): """Set the compartment of the node, or remove it from any compartment if -1 is given.""" net = _getNetwork(neti) node = _getNodeOrAlias(neti, nodei) _pushUndoStack() if node.compi != -1: net.compartments[node.compi].node_indices.remove(nodei) else: net.baseNodes.remove(nodei) if compi != -1: newComp = _getCompartment(neti, compi) newComp.node_indices.add(nodei) else: net.baseNodes.add(nodei) node.compi = compi
[docs]def setCompartmentPosition(neti: int, compi: int, x: float, y: float): if x < 0 or y < 0: _raiseError(-12) _pushUndoStack() comp = _getCompartment(neti, compi) comp.position = Vec2(x, y)
[docs]def getCompartmentPosition(neti: int, compi: int) -> Tuple[float, float]: comp = _getCompartment(neti, compi) return (comp.position.x, comp.position.y)
[docs]def setCompartmentSize(neti: int, compi: int, w: float, h: float): if w < 0 or h < 0: _raiseError(-12) _pushUndoStack() comp = _getCompartment(neti, compi) comp.rectSize = Vec2(w, h)
[docs]def getCompartmentSize(neti: int, compi: int) -> Tuple[float, float]: comp = _getCompartment(neti, compi) return (comp.rectSize.x, comp.rectSize.y)
[docs]def setCompartmentVolume(neti: int, compi: int, volume: float): _pushUndoStack() _getCompartment(neti, compi).volume = volume
[docs]def getCompartmentVolume(neti: int, compi: int) -> float: return _getCompartment(neti, compi).volume
[docs]def setCompartmentID(neti: int, compi: int, id: str): _pushUndoStack() _getCompartment(neti, compi).id = id
[docs]def getCompartmentID(neti: int, compi: int) -> str: return _getCompartment(neti, compi).id
# TODO note that this returns a Color instead of tuples of numbers. Should change the node & # reaction color functions to do the same.
[docs]def setCompartmentFillColor(neti: int, compi: int, color: Color): _pushUndoStack() _getCompartment(neti, compi).fillColor = color
[docs]def getCompartmentFillColor(neti: int, compi: int) -> Color: return _getCompartment(neti, compi).fillColor
[docs]def setCompartmentOutlineColor(neti: int, compi: int, color: Color): _pushUndoStack() _getCompartment(neti, compi).outlineColor = color
[docs]def getCompartmentOutlineColor(neti: int, compi: int) -> Color: return _getCompartment(neti, compi).outlineColor
[docs]def setCompartmentOutlineThickness(neti: int, compi: int, thickness: float): _pushUndoStack() _getCompartment(neti, compi).outlineThickness = thickness
[docs]def getCompartmentOutlineThickness(neti: int, compi: int) -> float: return _getCompartment(neti, compi).outlineThickness
[docs]def createUniUni(neti: int, reaID: str, rateLaw: str, srci: int, desti: int, srcStoich: float, destStoich: float): startGroup() createReaction(neti, reaID, [srci], [desti]) reai = getReactionIndex(neti, reaID) setReactionSrcNodeStoich(neti, reai, srci, srcStoich) setReactionDestNodeStoich(neti, reai, desti, destStoich) setRateLaw(neti, reai, rateLaw) endGroup()
# TODO allow modification of this list later # TODO make note that neti is useless here. Probably just remove the argument later # Also need to change getCompositeShapeAt
[docs]def getListOfCompositeShapes(neti: int): return [f.produce() for f in shapeFactories]
[docs]def getCompositeShapeAt(neti: int, shapei: int): return shapeFactories[shapei].produce()
[docs]def getNodeShape(neti: int, nodei: int) -> CompositeShape: return copy.copy(_getConcreteNode(neti, nodei).shape)
[docs]def getNodeShapeIndex(neti: int, nodei: int) -> int: return _getConcreteNode(neti, nodei).shapei
[docs]def setNodeShapeIndex(neti: int, nodei: int, shapei: int, preserve_common_fields=True): '''If preserve_common_fields is True, then preserve common field values such as fill_color, if applicable. (Not implemented) ''' net = _getNetwork(neti) node = _getConcreteNode(neti, nodei) node.shapei = shapei node.shape = shapeFactories[shapei].produce()
[docs]def setNodePrimitiveProperty(neti: int, nodei: int, prim_index: int, prop_name: str, prop_value: Any): '''Set an individual property of a node's primitive. Args: neti: The network index nodei: The node index prim_index: The index of the primitive, in the node's shape. If -1, then update the text primitive instead. prop_name: The name of the primitive's property. prop_value: The value of the primitives's property ''' node = _getConcreteNode(neti, nodei) if prim_index >= len(node.shape.items) or prim_index < -1: raise ValueError('Primitive index out of range for the shape of node {} in network {}'.format(nodei, neti)) if prim_index == -1: primitive, _transform = node.shape.text_item else: primitive, _transform = node.shape.items[prim_index] if prop_name not in primitive.__dataclass_fields__: raise ValueError('`{}` is not a property of primitive `{}`'.format( prop_name, primitive.__class__.__name__)) field = primitive.__dataclass_fields__[prop_name] # ensure that the assigned type is correct if not isinstance(prop_value, field.type): raise ValueError(f'Could not set primitive property `{prop_name}` of node {nodei} at ' f'primitive index {prim_index}. Expected object of type `{field.type.__name__}`; got ' f'`{prop_value}`(`{type(prop_value).__name__}`) instead. Note: the primitive ' f'is of type `{type(primitive).__name__}`.') # This is not very safe, but this is very simple to implement, so it shall be like this for now setattr(primitive, prop_name, prop_value)
[docs]def CreateUniBi(neti: int, reaID: str, rateLaw: str, srci: int, dest1i: int, dest2i: int, srcStoich: float, dest1Stoich: float, dest2Stoich: float): startGroup() createReaction(neti, reaID, [srci], [dest1i, dest2i]) reai = getReactionIndex(neti, reaID) setReactionSrcNodeStoich(neti, reai, srci, srcStoich) setReactionDestNodeStoich(neti, reai, dest1i, dest1Stoich) setReactionDestNodeStoich(neti, reai, dest2i, dest2Stoich) setRateLaw(neti, reai, rateLaw) endGroup()
[docs]def CreateBiUni(neti: int, reaID: str, rateLaw: str, src1i: int, src2i: int, desti: int, src1Stoich: float, src2Stoich: float, destStoich: float): startGroup() createReaction(neti, reaID, [src1i, src2i], [desti]) reai = getReactionIndex(neti, reaID) setReactionSrcNodeStoich(neti, reai, src1i, src1Stoich) setReactionSrcNodeStoich(neti, reai, src2i, src2Stoich) setReactionDestNodeStoich(neti, reai, desti, destStoich) setRateLaw(neti, reai, rateLaw) endGroup()
[docs]def CreateBiBi(neti: int, reaID: str, rateLaw: str, src1i: int, src2i: int, dest1i: int, dest2i: int, src1Stoich: float, src2Stoich: float, dest1Stoich: float, dest2Stoich: float): startGroup() createReaction(neti, reaID, [src1i, src2i], [dest1i, dest2i]) reai = getReactionIndex(neti, reaID) setReactionSrcNodeStoich(neti, reai, src1i, src1Stoich) setReactionSrcNodeStoich(neti, reai, src2i, src2Stoich) setReactionDestNodeStoich(neti, reai, dest1i, dest1Stoich) setReactionDestNodeStoich(neti, reai, dest2i, dest2Stoich) setRateLaw(neti, reai, rateLaw) endGroup()
[docs]def reset(): global stackFlag, errCode, networkDict, undoStack, redoStack, lastNetIndex stackFlag = True errCode = 0 networkDict = TNetworkDict() undoStack = TStack() redoStack = TStack() lastNetIndex = 0
'''Code for serialization/deserialization.'''
[docs]class EnumField(fields.Field): def __init__(self, enum_class): super().__init__() choices = [entry.value for entry in enum_class] for choice in choices: if not isinstance(choice, str): raise ValueError('The enum class given to EnumField must have string values!') self.enum_class = enum_class self.str_field = fields.Str(validate=validate.OneOf(choices)) def _serialize(self, entry, attr, obj, **kwargs): return entry.value def _deserialize(self, value, attr, data, **kwargs): self.str_field.validate(value) for entry in self.enum_class: if entry.value == value: return entry assert False, "Not supposed to reach here"
[docs]class ChoiceField(fields.Field): def __init__(self, choice_list): super().__init__() for choice in choice_list: if not isinstance(choice, ChoiceItem): raise ValueError("not choice item") self.choice_list = choice_list def _serialize(self, entry, attr, obj, **kwargs): for choice in self.choice_list: if entry == choice.value: return choice.text def _deserialize(self, value, attr, data, **kwargs): for choice in self.choice_list: if value == choice.text: return choice.value assert False, "No choice found"
[docs]class FontSchema(Schema): # TODO use this after implemented pointSize = Pixel() family = str # TODO change to enum style: str weight: str name: str color: Color
[docs]class TransformSchema(Schema): translation = Dim2() rotation = Dim() scale = Dim2()
[docs] @post_load def post_load(self, data: Any, **kwargs) -> Transform: return Transform(**data)
[docs]class PrimitiveSchema(Schema): name = fields.Str() fill_color = ColorField() border_color = ColorField() border_width = Dim()
[docs]class RectangleSchema(PrimitiveSchema): corner_radius = Dim()
[docs] @post_load def post_load(self, data: Any, **kwargs) -> RectanglePrim: del data['name'] return RectanglePrim(**data)
[docs]class CircleSchema(PrimitiveSchema):
[docs] @post_load def post_load(self, data: Any, **kwargs) -> CirclePrim: del data['name'] return CirclePrim(**data)
[docs]class PolygonSchema(PrimitiveSchema): #name = fields.Str() fill_color = ColorField() border_color = ColorField() border_width = Dim() corner_radius = Dim() radius = Dim()
[docs]class LineSchema(PrimitiveSchema): points = fields.Tuple(([Dim2()]*2))
[docs] @post_load def post_load(self, data: Any, **kwargs) -> LinePrim: del data['name'] return LinePrim(**data)
[docs]class TriangleSchema(PolygonSchema): points = fields.Tuple(((Dim2(),)*4))
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TrianglePrim: del data['name'] return TrianglePrim(**data)
[docs]class HexagonSchema(PolygonSchema): points = fields.Tuple((Dim2(),)*7)
[docs] @post_load def post_load(self, data: Any, **kwargs) -> HexagonPrim: del data['name'] return HexagonPrim(**data)
[docs]def primitive_dump(base_obj, parent_obj): ret = { RectanglePrim.__name__: RectangleSchema, CirclePrim.__name__: CircleSchema, LinePrim.__name__: LineSchema, TrianglePrim.__name__: TriangleSchema, HexagonPrim.__name__: HexagonSchema }[base_obj.__class__.__name__]() return ret
primitive_schemas = {'rectangle':RectangleSchema(), 'circle': CircleSchema(), 'triangle': TriangleSchema(), 'line': LineSchema(), 'hexagon': HexagonSchema()}
[docs]def primitive_load(base_dict, parent_dict): return primitive_schemas[base_dict['name']]
primitiveField = PolyField( serialization_schema_selector=primitive_dump, deserialization_schema_selector=primitive_load, required=True, )
[docs]class TextSchema(Schema): #name = fields.Str() bg_color = ColorField() font_color = ColorField() font_size = fields.Int() font_family = ChoiceField(FONT_FAMILY_CHOICES) font_style = ChoiceField(FONT_STYLE_CHOICES) font_weight = ChoiceField(FONT_WEIGHT_CHOICES) alignment = ChoiceField(TEXT_ALIGNMENT_CHOICES)
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TextPrim: return TextPrim(**data)
[docs]class CompositeShapeSchema(Schema): name = fields.Str() text_item = fields.Tuple((fields.Nested(TextSchema), fields.Nested(TransformSchema))) items = fields.List(fields.Tuple((primitiveField, fields.Nested(TransformSchema))))
[docs] @post_load def post_load(self, data: Any, **kwargs) -> CompositeShape: return CompositeShape(**data)
[docs]class AbstractNodeSchema(Schema): index = fields.Int() id = fields.Str() position = Dim2() rectSize = Dim2() nodeLocked = fields.Bool()
[docs] @post_dump def post_dump(self, data: Any, **kwargs): del data['index'] return data
[docs]class NodeSchema(AbstractNodeSchema): floating = fields.Bool() compi = fields.Int(missing=-1) shape = fields.Nested(CompositeShapeSchema)
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TNode: shape_name = data['shape'].name # get shape index manually # If this fails, then somebody modified the shape name shapei = [s.name for s in shapeFactories].index(shape_name) data['shapei'] = shapei return TNode(**data)
[docs]class AliasSchema(AbstractNodeSchema): originalIdx = fields.Int()
[docs] @post_load def post_load(self, data: Any, **kwargs): return TAliasNode(**data)
[docs]class SpeciesNode(Schema): """Represents a species in a reaction.""" stoich = fields.Float() handlePos = Dim2()
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TSpeciesNode: return TSpeciesNode(**data)
[docs]class ReactionSchema(Schema): id = fields.Str() centerPos = Dim2(missing=None) rateLaw = fields.Str() reactants = fields.Dict(fields.Int(), fields.Nested(SpeciesNode)) products = fields.Dict(fields.Int(), fields.Nested(SpeciesNode)) fillColor = ColorField() thickness = Dim() centerHandlePos = Dim2() bezierCurves = fields.Bool() modifiers = fields.List(fields.Int()) tipStyle = EnumField(ModifierTipStyle)
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TReaction: if 'modifiers' in data: data['modifiers'] = set(data['modifiers']) return TReaction(**data)
[docs]class CompartmentSchema(Schema): id = fields.Str() position = Dim2() rectSize = Dim2() nodes = fields.List(fields.Int()) volume = Dim() fillColor = ColorField() outlineColor = ColorField() outlineThickness = Dim()
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TCompartment: return TCompartment(**data)
[docs]def node_or_alias_dump(base_obj, parent_obj): ret = { TNode.__name__: NodeSchema, TAliasNode.__name__: AliasSchema, }[base_obj.__class__.__name__]() return ret
[docs]def node_or_alias_load(base_dict, parent_dict): if 'originalIdx' not in base_dict: return NodeSchema() else: return AliasSchema()
nodeOrAliasField = PolyField( serialization_schema_selector=node_or_alias_dump, deserialization_schema_selector=node_or_alias_load, required=True, )
[docs]class NetworkSchema(Schema): id = fields.Str() nodes = fields.Mapping(fields.Int(), nodeOrAliasField) reactions = fields.Mapping(fields.Int(), fields.Nested(ReactionSchema)) compartments = fields.Mapping(fields.Int(), fields.Nested(CompartmentSchema))
[docs] @pre_load def pre_load(self, data: Any, **kwargs): # load serialization version # this records the version of the serialization. Whenever the serialization scheme is changed, # we update the version number, so that if a user is trying to load a network with an # older serialization version than the current application (or vice versa), we can either fail # to try to do some conversion. serial_version = data.get('serialVersion', 'pre-release') if serial_version != SERIAL_VERSION: # we have a mismatch print(("Warning: loaded network has serial version '{}', which does not match that of " "the application: '{}'").format(serial_version, SERIAL_VERSION)) if 'serialVersion' in data: del data['serialVersion'] # populate the index field of nodes. This is a redundancy, so we don't expect this field # to be serialized/deserialized for nodei, nodedata in data['nodes'].items(): nodedata['index'] = int(nodei) return data
[docs] @post_load def post_load(self, data: Any, **kwargs) -> TNetwork: return TNetwork(**data)
[docs] @post_dump def post_dump(self, data, **kwargs): data['serialVersion'] = SERIAL_VERSION return data
net_schema = NetworkSchema()
[docs]def dumpNetwork(neti: int): """Dump the network into an object and return it.""" # TODO don't construct NetworkSchema every time. net = _getNetwork(neti) return net_schema.dump(net)
[docs]def loadNetwork(net_object) -> int: """Load the network object (laoded directly from JSON) and add it, returning the network index. Note: For now this overwrites the network at index 0. """ # NOTE marshmallow::load does not guarantee that the input object is not modified. Therefore, # we need to make a deepcopy obj_copy = copy.deepcopy(net_object) net = net_schema.load(obj_copy) clearNetworks() _addNetwork(net) return 0
[docs]def validateState(): assert undoStack is not None assert redoStack is not None net_ids = [net.id for net in networkDict.values()] assert len(net_ids) == len(set(net_ids)), "duplicate network IDs" assert isinstance(networkDict, dict) for neti, net in networkDict.items(): assert isinstance(neti, int) assert isinstance(net, TNetwork) validateNodes(net.nodes)
# TODO validate reactions, compartments, and cross-validate
[docs]def validateNodes(nodes: Dict[int, TAbstractNode]): assert isinstance(nodes, dict) for nodei, node in nodes.items(): assert isinstance(nodei, int) assert isinstance(node, TNode) or isinstance(node, TAliasNode) assert isinstance(node, TNode) or isinstance(node, TAliasNode), 'unexpected type: ' + type(node) cnodes = [n for n in nodes.values() if isinstance(n, TNode)] aliases = [n for n in nodes.values() if isinstance(n, TAliasNode)] node_ids = [n.id for n in cnodes] assert len(node_ids) == len(set(node_ids)), "duplicate node IDs" # assert alias references are good for alias in aliases: assert isinstance(alias.originalIdx, int) and alias.originalIdx >= 0 assert alias.originalIdx in nodes and isinstance(nodes[alias.originalIdx], TNode) for cnode in cnodes: assert isinstance(cnode.floating, bool) for node in nodes.values(): node: TAbstractNode assert isinstance(node.nodeLocked, bool) assert isinstance(node.compi, int) assert isinstance(node.position, Vec2) and node.position.x >= 0 and node.position.y >= 0 assert isinstance(node.rectSize, Vec2) and node.rectSize.x >= 0 and node.rectSize.y >= 0
# TODO assert node.compi in net.compartments OUTSIDE of this function # TODO assert more properties