SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.pmm.nodes/src/de/bund/bfr/knime/pmm/sbmlwriter/SBMLWriterNodeModel.java

Summary

Maintainability
D
2 days
Test Coverage
/*******************************************************************************
 * Copyright (c) 2015 Federal Institute for Risk Assessment (BfR), Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Department Biological Safety - BfR
 *******************************************************************************/
package de.bund.bfr.knime.pmm.sbmlwriter;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLStreamException;

import org.knime.core.data.DataTableSpec;
import org.knime.core.node.BufferedDataTable;
import org.knime.core.node.CanceledExecutionException;
import org.knime.core.node.ExecutionContext;
import org.knime.core.node.ExecutionMonitor;
import org.knime.core.node.InvalidSettingsException;
import org.knime.core.node.NodeModel;
import org.knime.core.node.NodeSettingsRO;
import org.knime.core.node.NodeSettingsWO;
import org.sbml.jsbml.ASTNode;
import org.sbml.jsbml.AssignmentRule;
import org.sbml.jsbml.Compartment;
import org.sbml.jsbml.Constraint;
import org.sbml.jsbml.Creator;
import org.sbml.jsbml.History;
import org.sbml.jsbml.ListOf;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.Parameter;
import org.sbml.jsbml.Rule;
import org.sbml.jsbml.SBMLDocument;
import org.sbml.jsbml.SBMLWriter;
import org.sbml.jsbml.Species;
import org.sbml.jsbml.Unit;
import org.sbml.jsbml.UnitDefinition;
import org.sbml.jsbml.text.parser.FormulaParser;
import org.sbml.jsbml.text.parser.ParseException;

import de.bund.bfr.knime.pmm.common.AgentXml;
import de.bund.bfr.knime.pmm.common.CatalogModelXml;
import de.bund.bfr.knime.pmm.common.DepXml;
import de.bund.bfr.knime.pmm.common.EstModelXml;
import de.bund.bfr.knime.pmm.common.IndepXml;
import de.bund.bfr.knime.pmm.common.MatrixXml;
import de.bund.bfr.knime.pmm.common.ModelCombiner;
import de.bund.bfr.knime.pmm.common.ParamXml;
import de.bund.bfr.knime.pmm.common.PmmXmlDoc;
import de.bund.bfr.knime.pmm.common.PmmXmlElementConvertable;
import de.bund.bfr.knime.pmm.common.generictablemodel.KnimeSchema;
import de.bund.bfr.knime.pmm.common.generictablemodel.KnimeTuple;
import de.bund.bfr.knime.pmm.common.math.MathUtilities;
import de.bund.bfr.knime.pmm.common.pmmtablemodel.AttributeUtilities;
import de.bund.bfr.knime.pmm.common.pmmtablemodel.Model1Schema;
import de.bund.bfr.knime.pmm.common.pmmtablemodel.PmmUtilities;
import de.bund.bfr.knime.pmm.common.pmmtablemodel.SchemaFactory;
import de.bund.bfr.knime.pmm.common.pmmtablemodel.TimeSeriesSchema;
import de.bund.bfr.knime.pmm.common.units.Categories;
import de.bund.bfr.knime.pmm.common.units.Category;
import de.bund.bfr.knime.pmm.common.units.ConvertException;

/**
 * This is the model implementation of SBMLWriter.
 *
 * @author Christian Thoens
 * @author Miguel de Alba
 */
public class SBMLWriterNodeModel extends NodeModel {

    private final SBMLWriterNodeSettings nodeSettings = new SBMLWriterNodeSettings();

    private KnimeSchema schema;

    /**
     * Constructor for the node model.
     */
    protected SBMLWriterNodeModel() {
        super(1, 0);
        schema = null;
    }

    @Override
    protected BufferedDataTable[] execute(final BufferedDataTable[] inData, final ExecutionContext exec)
            throws Exception {
        TableReader reader = new TableReader(PmmUtilities.getTuples(inData[0], schema), nodeSettings.variableParams,
                nodeSettings.creatorGivenName, nodeSettings.creatorFamilyName, nodeSettings.creatorContact,
                new Date(nodeSettings.createdDate), new Date(nodeSettings.modifiedDate), nodeSettings.reference);
        Map<String, File> files = new LinkedHashMap<>();

        for (String name : reader.getDocuments().keySet()) {
            File file = new File(nodeSettings.outPath + "/" + name + ".sbml.xml");

            if (!nodeSettings.overwrite && file.exists()) {
                throw new IOException(file.getAbsolutePath() + " already exists");
            }

            files.put(name, file);
        }

        for (String name : reader.getDocuments().keySet()) {
            SBMLWriter.write(reader.getDocuments().get(name), files.get(name), name, "1.0");
        }

        return new BufferedDataTable[] {};
    }

    @Override
    protected void reset() {
    }

    @Override
    protected DataTableSpec[] configure(final DataTableSpec[] inSpecs) throws InvalidSettingsException {
        if (SchemaFactory.createM12DataSchema().conforms(inSpecs[0])) {
            schema = SchemaFactory.createM12DataSchema();
        } else if (SchemaFactory.createM1DataSchema().conforms(inSpecs[0])) {
            schema = SchemaFactory.createM1DataSchema();
        } else if (nodeSettings.outPath == null || nodeSettings.variableParams == null) {
            throw new InvalidSettingsException("Node must be configured");
        } else {
            throw new InvalidSettingsException("Invalid Input");
        }

        return new DataTableSpec[] {};
    }

    @Override
    protected void saveSettingsTo(final NodeSettingsWO settings) {
        nodeSettings.save(settings);
    }

    @Override
    protected void loadValidatedSettingsFrom(final NodeSettingsRO settings) throws InvalidSettingsException {
        nodeSettings.load(settings);
    }

    @Override
    protected void validateSettings(final NodeSettingsRO settings) throws InvalidSettingsException {
        // Does nothing
    }

    @Override
    protected void loadInternals(final File internDir, final ExecutionMonitor exec)
            throws IOException, CanceledExecutionException {
    }

    @Override
    protected void saveInternals(final File internDir, final ExecutionMonitor exec)
            throws IOException, CanceledExecutionException {
    }

    private static class TableReader {

        private final static String COMPARTMENT_MISSING = "CompartmentMissing";
        private final static String SPECIES_MISSING = "SpeciesMissing";

        private Map<String, SBMLDocument> documents;

        public TableReader(List<KnimeTuple> tuples, String varParams, String creatorGivenName, String creatorFamilyName,
                String creatorContact, Date createdDate, Date modifiedDate, String reference)
                throws IOException, XMLStreamException {
            boolean isTertiaryModel = tuples.get(0).getSchema().conforms(SchemaFactory.createM12Schema());
            Set<Integer> idSet = new LinkedHashSet<>();
            Map<KnimeTuple, List<KnimeTuple>> tupleMap;

            if (isTertiaryModel) {
                tupleMap = new ModelCombiner(tuples, true, null, null).getTupleCombinations();
            } else {
                tupleMap = new LinkedHashMap<>();

                for (KnimeTuple tuple : tuples) {
                    tupleMap.put(tuple, Arrays.asList(tuple));
                }
            }

            documents = new LinkedHashMap<>();

            for (KnimeTuple tuple : tupleMap.keySet()) {
                replaceCelsiusAndFahrenheit(tuple);
                renameLog(tuple);

                CatalogModelXml modelXml = (CatalogModelXml) tuple.getPmmXml(Model1Schema.ATT_MODELCATALOG).get(0);
                EstModelXml estXml = (EstModelXml) tuple.getPmmXml(Model1Schema.ATT_ESTMODEL).get(0);
                DepXml depXml = (DepXml) tuple.getPmmXml(Model1Schema.ATT_DEPENDENT).get(0);
                AgentXml organismXml = (AgentXml) tuple.getPmmXml(TimeSeriesSchema.ATT_AGENT).get(0);
                MatrixXml matrixXml = (MatrixXml) tuple.getPmmXml(TimeSeriesSchema.ATT_MATRIX).get(0);
                Integer id = estXml.id;

                if (!idSet.add(id)) {
                    continue;
                }

                History history = new History();

                if (createdDate != null) {
                    history.setCreatedDate(createdDate);
                }

                if (modifiedDate != null) {
                    history.setModifiedDate(modifiedDate);
                }

                history.addCreator(new Creator(creatorGivenName, creatorFamilyName, null, creatorContact));

                String modelName = ((EstModelXml) tupleMap.get(tuple).get(0).getPmmXml(Model1Schema.ATT_ESTMODEL)
                        .get(0)).name;

                if (modelName == null || modelName.trim().isEmpty()) {
                    throw new IOException("Model Name missing");
                }

                String modelID = createId(modelName);
                SBMLDocument doc = new SBMLDocument(2, 4);
                Model model = doc.createModel(modelID);

                model.setMetaId("Meta_" + modelID);
                model.setName(modelID);
                model.setHistory(history);
                // model.setNotes(XMLNode.convertStringToXMLNode("<notes>" +
                // reference
                // + "</notes>"));

                Compartment c;
                Species s;

                if (matrixXml.name != null) {
                    c = model.createCompartment(createId(matrixXml.name));
                    c.setName(matrixXml.name);
                } else {
                    c = model.createCompartment(COMPARTMENT_MISSING);
                    c.setName(COMPARTMENT_MISSING);
                }

                if (organismXml.name != null) {
                    s = model.createSpecies(createId(organismXml.name), c);
                    s.setName(organismXml.name);
                } else {
                    s = model.createSpecies(SPECIES_MISSING, c);
                    s.setName(SPECIES_MISSING);
                }

                ListOf<Rule> rules = new ListOf<>(2, 4);
                ListOf<Constraint> constraints = new ListOf<>(2, 4);
                Parameter depParam = model.createParameter(depXml.name);
                String depSbmlUnit = Categories.getCategoryByUnit(depXml.unit).getSBML(depXml.unit);

                depParam.setValue(0.0);
                depParam.setConstant(false);

                if (depSbmlUnit != null) {
                    UnitDefinition unit = SBMLUtilities.fromXml(depSbmlUnit);
                    Unit.Kind kind = SBMLUtilities.simplify(unit);
                    UnitDefinition modelUnit = model.getUnitDefinition(unit.getId());

                    if (kind != null) {
                        depParam.setUnits(kind);
                    } else if (modelUnit != null) {
                        depParam.setUnits(modelUnit);
                    } else {
                        depParam.setUnits(SBMLUtilities.addUnitToModel(model, unit));
                    }
                }

                String formula = modelXml.formula.substring(modelXml.formula.indexOf("=") + 1);

                try {
                    rules.add(new AssignmentRule(parse(formula), depParam));
                } catch (ParseException e) {
                    e.printStackTrace();
                }

                for (PmmXmlElementConvertable el : tuple.getPmmXml(Model1Schema.ATT_PARAMETER).getElementSet()) {
                    ParamXml paramXml = (ParamXml) el;
                    Parameter param = model.createParameter(paramXml.name);

                    if (paramXml.name.equals(varParams)) {
                        param.setConstant(false);
                    } else {
                        param.setConstant(true);
                    }

                    if (paramXml.value != null) {
                        param.setValue(paramXml.value);
                    } else {
                        param.setValue(0.0);
                    }
                }

                for (PmmXmlElementConvertable el : tuple.getPmmXml(Model1Schema.ATT_INDEPENDENT).getElementSet()) {
                    IndepXml indepXml = (IndepXml) el;

                    if (indepXml.name.equals(AttributeUtilities.TIME)) {
                        indepXml.name = "time";
                    }

                    String name = indepXml.name;
                    Parameter param = model.createParameter(name);
                    String sbmlUnit = Categories.getCategoryByUnit(indepXml.unit).getSBML(indepXml.unit);

                    param.setValue(0.0);
                    param.setConstant(false);

                    Double min = indepXml.min;
                    Double max = indepXml.max;

                    if (MathUtilities.isValid(min)) {
                        try {
                            constraints.add(new Constraint(parse(name + ">=" + min), 2, 4));
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }

                    if (MathUtilities.isValid(max)) {
                        try {
                            constraints.add(new Constraint(parse(name + "<=" + max), 2, 4));
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }

                    if (sbmlUnit != null) {
                        UnitDefinition unit = SBMLUtilities.fromXml(sbmlUnit);
                        Unit.Kind kind = SBMLUtilities.simplify(unit);
                        UnitDefinition modelUnit = model.getUnitDefinition(unit.getId());

                        if (kind != null) {
                            param.setUnits(kind);
                        } else if (modelUnit != null) {
                            param.setUnits(modelUnit);
                        } else {
                            param.setUnits(SBMLUtilities.addUnitToModel(model, unit));
                        }
                    }
                }

                model.setListOfRules(rules);
                model.setListOfConstraints(constraints);

                if (documents.containsKey(modelID)) {
                    throw new IOException("Duplicate model name: " + modelID);
                }

                documents.put(modelID, doc);
            }
        }

        public Map<String, SBMLDocument> getDocuments() {
            return documents;
        }

        private static void renameLog(KnimeTuple tuple) {
            PmmXmlDoc modelXml = tuple.getPmmXml(Model1Schema.ATT_MODELCATALOG);
            CatalogModelXml model = (CatalogModelXml) modelXml.get(0);

            model.formula = MathUtilities.replaceVariable(model.formula, "log", "log10");
            tuple.setValue(Model1Schema.ATT_MODELCATALOG, modelXml);
        }

        private static void replaceCelsiusAndFahrenheit(KnimeTuple tuple) {
            final String CELSIUS = "°C";
            final String FAHRENHEIT = "°F";
            final String KELVIN = "K";

            PmmXmlDoc indepXml = tuple.getPmmXml(Model1Schema.ATT_INDEPENDENT);
            PmmXmlDoc modelXml = tuple.getPmmXml(Model1Schema.ATT_MODELCATALOG);
            CatalogModelXml model = (CatalogModelXml) modelXml.get(0);
            Category temp = Categories.getTempCategory();

            for (PmmXmlElementConvertable el : indepXml.getElementSet()) {
                IndepXml indep = (IndepXml) el;

                if (CELSIUS.equals(indep.unit)) {
                    try {
                        String replacement = "(" + temp.getConversionString(indep.name, KELVIN, CELSIUS) + ")";

                        model.formula =
                                MathUtilities.replaceVariable(model.formula, indep.name, replacement);
                        indep.unit = KELVIN;
                        indep.min = temp.convert(indep.min, CELSIUS, KELVIN);
                        indep.max = temp.convert(indep.max, CELSIUS, KELVIN);
                    } catch (ConvertException e) {
                        e.printStackTrace();
                    }
                } else if (FAHRENHEIT.equals(indep.unit)) {
                    try {
                        String replacement = "(" + temp.getConversionString(indep.name, KELVIN, FAHRENHEIT) + ")";

                        model.formula = 
                                MathUtilities.replaceVariable(model.formula, indep.name, replacement);
                        indep.unit = FAHRENHEIT;
                        indep.min = temp.convert(indep.min, FAHRENHEIT, KELVIN);
                        indep.max = temp.convert(indep.max, FAHRENHEIT, KELVIN);
                    } catch (ConvertException e) {
                        e.printStackTrace();
                    }
                }
            }

            tuple.setValue(Model1Schema.ATT_INDEPENDENT, indepXml);
            tuple.setValue(Model1Schema.ATT_MODELCATALOG, modelXml);
        }

        private static String createId(String s) {
            return s.replaceAll("\\W+", " ").trim().replace(" ", "_");
        }

        private static ASTNode parse(String s) throws ParseException {
            return new FormulaParser(new StringReader(s)).parse();
        }
    }
}