<jupyter_start><jupyter_text>go from molybdenum to simpleSBML Problem with the simpleSBML representation:I want something fluid, like the user should be able to change the name or anything of the anything, which is the ID in simpleSBML, and still keep the representation. simpleSBML does not have Ids so I can't have this fluid behavior. I use a representation which keeps this information, same as simpleSBML but in a dictionary, which makes it readable, and also some information related to nodes. Since the structure is very simple and integrates with simpleSBML I can get all functionality from simpleSBML like exporting to SBML, antimony or tellurium directly. I know the name and Id gets a bit confusing but it makes a lot of sense. The Id of species or reactions is equivalent to the ID in the nodes, with the difference than ID of the nodes must be an integer, while having the ID lets the user have some more freedom. I guess it might be possible to use this technique and correlate the name of the species with the node id?? But I am afraid this would make updates risky and technically difficult, I can see when I get to that part... How the molybdenum representation should look like:```pythonmod_data = {'species': {'spec1': {'name': 'E', 'concentration': 10.0, 'fixed': False},  if name starts with $ will set as fixed                        'spec2': {'name': 'S', 'concentration': 10.0, 'fixed': False},                        'spec3': {'name': 'ES', 'concentration': 10.0, 'fixed': False},                        'spec4': {'name': 'P', 'concentration': 10.0, 'fixed': False}},            'reactions': {'reac5': {'name': 'R1', 'reagents': ['E', 'S'], 'products': ['ES'], 'reversible': False},                          'reac6': {'name': 'R2', 'reagents': ['ES'], 'products': ['P'], 'reversible': False}},            'params': {'param1': {'name': 'Kcat', 'value': 12}, units in the future                       'param2': {'name': 'Kmax', 'value': 10}},             optionally, can include simulation parameters and node_to_id values            'node_to_id': {1: 'spec1',                           2: 'spec2',                           3: 'spec3',                           4: 'spec4',                           5: 'spec5'},            'sim_params': {'init': 0, 'fin': 10, 'steps': 200}           }``` This is the model I will try to emulate here:```pythonimport simplesbmlmodel = simplesbml.SbmlModel()model.addSpecies('E', 5e-21)model.addSpecies('S', 1e-20)model.addSpecies('ES', 0.0)model.addSpecies('P', 0.0)model.addParameter('koff', 0.2)model.addParameter('kon', 1000000.0)model.addParameter('kcat', 0.1)model.addReaction(['E', 'S'], ['ES'], '(kon*E*S-koff*ES)', rxn_id='veq')model.addReaction(['ES'], ['E', 'P'], 'kcat*ES', rxn_id='vcat')``` Input in molybdenum format<jupyter_code>mod_data = {'species': {'spec1': {'name': 'E', 'amt': 5e-21, 'fixed': False}, # if name starts with $ will set as fixed
                        'spec2': {'name': 'S', 'amt': 1e-20, 'fixed': False},
                        'spec3': {'name': 'ES', 'amt': 0.0, 'fixed': False},
                        'spec4': {'name': 'P', 'amt': 0.0, 'fixed': False}},
            'reactions': {'reac5': {'name': 'veq', 'reagents': ['E', 'S'], 'products': ['ES'], 'expression': '(kon*E*S-koff*ES)'},
                          'reac6': {'name': 'vcat', 'reagents': ['ES'], 'products': ['P'], 'expression': 'kcat*ES'}},
            'params': {'param1': {'name': 'koff', 'val': 0.2}, #units in the future
                       'param2': {'name': 'kon', 'val': 1e7},
                       'param3': {'name': 'kcat', 'val': 0.1}}}
def create_ids(mb_model):
    """
    Assigns arbitrary node ids based on species and reactions of a molybdenum model
    
    Inputs
      mb_model: nested dictionary in molybdenum format with species and reactions defined, at least
    
    Outputs
      node_to_id: dictionary with:
         keys: integers corresponding to node ids
         values: corresponding ids in the molybdenum model
    """    
    try:
        type(mb_model['species']) == dict
    except:
        raise ValueError(f'Molybdenum model must have a "species" key with a dictionary value')
    
    try:
        type(mb_model['reactions']) == dict
    except:
        raise ValueError(f'Molybdenum model must have a "reactions" key with a dictionary value')
    
    node_to_id = dict()
    mb_ids = list(mb_model['species'].keys()) + list(mb_model['reactions'].keys())
    
    for node_id, mb_id in enumerate(mb_ids, 1):
        node_to_id[node_id] = mb_id

    return node_to_id
create_ids(mod_data)
class ModelRepresentation(object):
    def __init__(self):
        self.species = dict()
        self.reactions = dict()
        self.params = dict()
        self.node_to_id = dict()
        self.sim_params = dict()
        # not implemented yet
        # self.events = dict()

    def loadm(self, molybdenum_model):
        """Note this is not an update, erases everything that was there previously
        
        TODO: needs to be more elaborate. What if no concentration is passed as default for species? error or assign one?
        TODO: what if an specie starts with $ symbol, should we turn Fixed=True automatically? I would say, yes! and raise error if Fixed was explicitly false but $ is in name, this would cause confusion
        TODO: perform a check that all ids (keys in dictionaries) are unique for species, params and reactions, if not invalid format
        """
        self.species = molybdenum_model['species']
        self.reactions = molybdenum_model['reactions']
        self.params = molybdenum_model['params']
        # some are optional
        if 'node_to_id' in molybdenum_model.keys():
            self.node_to_id = molybdenum_model['node_to_id']
        else:
            # if not there, create it from ids in the model
            self.node_to_id = create_ids(molybdenum_model)
        if 'sim_params' in molybdenum_model.keys():
            self.sim_params = molybdenum_model['sim_params']
        else:
            # keep empty, if user tries to use it, will raise error
            self.sim_params = dict()
    
    def todict(self):
        """
        Get all information in a nested dictionary
        """
        return self.__dict__
        
    def tojson(self):
        """
        In Json things are double quoted and false is in lowercase
        """
        import json
        json_rep = json.dumps(self.todict())
        return json_rep
    
    def tosimpleSbml(self):
        """Need to write this up now"""
        #TODO: is importing inside the function correct?
        import simplesbml
        # initialize a model
        simpSbml_rep = simplesbml.SbmlModel()
        
        # add species
        for spec in self.species.values():
            # if species is fixed, add $ sign to its name (if not there already) so that
            # simpleSbml understands it is boundary / fixed
            if spec['fixed'] and spec['name'][0] != '$':
                spec_name = '$' + spec['name']
            else:
                spec_name = spec['name']
            # component support in the future
            simpSbml_rep.addSpecies(species_id = spec_name, amt = spec['amt'])#, comp='c1')
        
        # add reactions
        for reac in self.reactions.values():
            simpSbml_rep.addReaction(reactants=reac['reagents'],
                                     products=reac['products'],
                                     expression=reac['expression'],
                                     rxn_id=reac['name'])

        # add parameters
        for param in self.params.values():
            simpSbml_rep.addParameter(param_id=param['name'],
                                      val=param['val'],
                                      units='per_second') # unit support not yet

        return simpSbml_rep

    def toSBMLstr(self):
        # gets smbl
        simpSbml_rep = self.tosimpleSbml()
        #toSBML is also a function from simpleSBML models that gets the sbml string, (confusing?)
        sbml_str = simpSbml_rep.toSBML()
        return sbml_str

    def tosimpleSbmlWriteup(self):
        import simplesbml
        sbml_rep = self.toSBMLstr()
        sbml_writeup = simplesbml.simplesbml.writeCodeFromString(sbml_rep)
        return sbml_writeup

    def toAntimony(self):
        import tellurium as te
        r = te.antimonyConverter()
        sbml_str = self.toSBMLstr()
        sb_rep = r.sbmlToAntimony(sbml_str)[1]
        return sb_rep

    def toGraph(self):
        """
        Need to write this one up
        #TODO: for this, new X and Y values should be blank or zero, leave it up for further development
        """      
        # initialize object to keep graph
        graph_rep = {'nodes': [], 'edges': []}
        
        # fill in node information
        for node_id, mb_id in self.node_to_id.items():
            # search for information in species or reactions
            if mb_id in self.species.keys():
                title = self.species[mb_id]['name']
                node_class = 'species'
            elif mb_id in self.reactions.keys():
                title = self.reactions[mb_id]['name']
                node_class = 'reaction'
            else:
                raise ValueError(f'Could not find molybdenum id {mb_id} from node_to_id in species or reactions')

            # keep information
            node_info = {
                'id': node_id,
                'title': title,
                'x': 0.0,
                'y': 0.0,
                'nodeClass': node_class
            }
            graph_rep['nodes'].append(node_info)
            
        # fill in edge information from reactions
        # inverse the information in nodes_to_id
        id_to_nodes = {v: k for k, v in self.node_to_id.items()}
        # relate each species name to its id
        spec_name_to_id = {spec_info['name']: spec_id for spec_id, spec_info in self.species.items()}
        # then iterate by each reaction
        for reac_mb_id, reac in self.reactions.items():
            # first reagents
            for reagent in reac['reagents']:
                # source is the species
                source = id_to_nodes[spec_name_to_id[reagent]]
                # target is the reaction
                target = id_to_nodes[reac_mb_id]
                # keep information
                graph_rep['edges'].append({'source': source,
                                           'target': target})
            # then products
            for product in reac['products']:
                # source is the reaction
                source = id_to_nodes[reac_mb_id]
                # target is the species
                target = id_to_nodes[spec_name_to_id[product]]
                # keep information
                graph_rep['edges'].append({'source': source,
                                           'target': target})

        return graph_rep<jupyter_output><empty_output><jupyter_text>Initialize model with the molybdenum model<jupyter_code>mb_model = ModelRepresentation()
mb_model.loadm(mod_data)<jupyter_output><empty_output><jupyter_text>I can also explore it as a molybdenum model<jupyter_code>mb_model.species
mb_model.reactions
mb_model.params
mb_model.node_to_id
mb_model.todict()
mb_model.tojson()<jupyter_output><empty_output><jupyter_text>Then, I can use this model as if it was a simple_sbml model<jupyter_code>simple_sbml = mb_model.tosimpleSbml()
simple_sbml.getParameterValue('koff')
simple_sbml.getListOfReactionIds()<jupyter_output><empty_output><jupyter_text>I can also convert it to the other formats  - SBML<jupyter_code>print(mb_model.toSBMLstr())<jupyter_output><?xml version="1.0" encoding="UTF-8"?>
<sbml xmlns="http://www.sbml.org/sbml/level3/version1/core" level="3" version="1">
  <model substanceUnits="mole" timeUnits="second" extentUnits="mole">
    <listOfUnitDefinitions>
      <unitDefinition id="per_second">
        <listOfUnits>
          <unit kind="second" exponent="-1" scale="0" multiplier="1"/>
        </listOfUnits>
      </unitDefinition>
    </listOfUnitDefinitions>
    <listOfCompartments>
      <compartment id="c1" spatialDimensions="3" size="1" units="litre" constant="true"/>
    </listOfCompartments>
    <listOfSpecies>
      <species id="E" compartment="c1" initialAmount="5e-21" substanceUnits="mole" hasOnlySubstanceUnits="false" boundaryCondition="false" constant="false"/>
      <species id="S" compartment="c1" initialAmount="1e-20" substanceUnits="mole" hasOnlySubstanceUnits="false" boundaryCondition="false" constant="false"/>
      <species id="ES" compartment="c1" initialAmount="0" substanceUnits="mole" hasOnlySubstanc[...]<jupyter_text>- Write up of simpleSBML   TODO: This will be useful to print in a text box, that the user can then edit, and resubmit to make changes to the model procedurally. The user will have also the option to make this modifications in antimony from a different text box (they should be like dropdowns)<jupyter_code>print(mb_model.tosimpleSbmlWriteup())<jupyter_output>import simplesbml
model = simplesbml.sbmlModel();
model.addSpecies(species_id='E', amt=5e-21);
model.addSpecies(species_id='S', amt=1e-20);
model.addSpecies(species_id='ES', amt=0.0);
model.addSpecies(species_id='P', amt=0.0);
model.addParameter(param_id='koff', val=0.2);
model.addParameter(param_id='kon', val=10000000.0);
model.addParameter(param_id='kcat', val=0.1);
model.addReaction(reactants=['E', 'S'], products=['ES'], expression='kon * E * S - koff * ES', rxn_id='veq');
model.addReaction(reactants=['ES'], products=['P'], expression='kcat * ES', rxn_id='vcat');<jupyter_text>- To antimony<jupyter_code>print(mb_model.toAntimony())<jupyter_output>// Created by libAntimony v2.12.0
model *doc0()

  // Compartments and Species:
  compartment c1;
  species E in c1, S in c1, ES in c1, P in c1;

  // Reactions:
  veq: E + S => ES; kon*E*S - koff*ES;
  vcat: ES => P; kcat*ES;

  // Species initializations:
  E = 5e-21/c1;
  E has mole_per_litre;
  S = 1e-20/c1;
  S has mole_per_litre;
  ES = 0;
  ES has mole_per_litre;
  P = 0;
  P has mole_per_litre;

  // Compartment initializations:
  c1 = 1;
  c1 has litre;

  // Variable initializations:
  koff = 0.2;
  koff has per_second;
  kon = 10000000;
  kon has per_second;
  kcat = 0.1;
  kcat has per_second;

  // Other declarations:
  var koff, kon, kcat;
  const c1;

  // Unit definitions:
  unit per_second = 1 / second;
  unit substance = mole;
  unit extent = mole;
  unit time_unit = second;
  unit mole_per_litre = mole / litre;
end<jupyter_text>- I also want to get a graphic representation that looks like this (except for x and y elements, which shall be empty) ```pythoncon_data = {'nodes':  [{'id': 1, 'title': 'E', 'x': 228.1999969482422, 'y': 169.60000610351562, 'nodeClass': 'species'},                       {'id': 2, 'title': 'S', 'x': 227.1999969482422, 'y': 323.6000061035156, 'nodeClass': 'species'},                       {'id': 3, 'title': 'ES', 'x': 615.2000122070312, 'y': 226.60000610351562, 'nodeClass': 'species'},                       {'id': 4, 'title': 'P', 'x': 959.2000122070312, 'y': 222.60000610351562, 'nodeClass': 'species'},                       {'id': 5, 'title': 'veq', 'x': 400.20001220703125, 'y': 229.60000610351562, 'nodeClass': 'reaction'},                       {'id': 6, 'title': 'vcat', 'x': 776.2000122070312, 'y': 229.60000610351562, 'nodeClass': 'reaction'}],             'edges': [{'source': 1, 'target': 5},                        {'source': 2, 'target': 5},                       {'source': 5, 'target': 3},                       {'source': 3, 'target': 6},                       {'source': 6, 'target': 4}]}```<jupyter_code>mb_model.toGraph()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12453956560396291, 'fraction_numerical': 0.025022227867394893, 'mean_word_length': 2.5804911323328787, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 1, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 1, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '465830', 'license': 'MIT', 'n_tokens_mistral': 5133, 'n_tokens_neox': 4761, 'n_words': 1418, 'path': 'notebooks/1_MolybToSimpleSBML.ipynb', 'repo_name': 'mexposit/molybdenum', 'size': '15746'}