/******************************************************************************* * Copyright (c) 2009 University of Edinburgh. * All rights reserved. This program and the accompanying materials are made * available under the terms of the BSD Licence, which accompanies this feature * and can be downloaded from http://groups.inf.ed.ac.uk/pepa/update/licence.txt ******************************************************************************/ package uk.ac.ed.inf.biopepa.core.analysis; import java.util.*; import java.util.Map.Entry; import uk.ac.ed.inf.biopepa.core.compiler.*; import uk.ac.ed.inf.biopepa.core.compiler.PrefixData.Operator; import uk.ac.ed.inf.biopepa.core.compiler.ProblemInfo.Severity; public class ReactantRateParticipationCheck { private ModelCompiler compiledModel; private SystemEquationVisitor sev = new SystemEquationVisitor(); private ReactantParticipantVisitor rrp = new ReactantParticipantVisitor(); private List<ProblemInfo> problems = new ArrayList<ProblemInfo>(); private ReactantRateParticipationCheck() { } static List<ProblemInfo> checkActions(ModelCompiler compiledModel) { ReactantRateParticipationCheck rrpc = new ReactantRateParticipationCheck(); rrpc.compiledModel = compiledModel; rrpc.sev.visit(compiledModel.getSystemEquation()); rrpc.checkExternalParticipants(); return rrpc.problems; } public void checkExternalParticipants(){ // Get all the functional rates and // make a map from action names to sets of external participants HashMap<FunctionalRateData, HashSet<String>> actionMap = new HashMap<FunctionalRateData, HashSet<String>> (); Map<String, FunctionalRateData> frdMap = compiledModel.getFunctionalRateMap(); for (Entry<String, FunctionalRateData> entry : frdMap.entrySet()){ FunctionalRateData rateData = entry.getValue(); ExternalParticipationVisitor epv = new ExternalParticipationVisitor(); rateData.getRightHandSide().accept(epv); actionMap.put(rateData, epv.getParticipants()); } // Check each component definition; for every action name // the component should either *not* appear in the set of external // participants OR should contain the action in its definition. for (ComponentData compdata : compiledModel.getComponents()){ // The set of reactions that the component is a reactant in HashSet<String> isReactants = componentReactantReactions(compdata); // So for every reaction we check if this component // is an external participant, that is does it affect the rate // other than through fMA etc. for (Entry<FunctionalRateData, HashSet<String>> entry : actionMap.entrySet()){ // if component affects the rate of reaction // and component is not a reaction FunctionalRateData rateData = entry.getKey(); String reactionName = rateData.getName(); String compName = compdata.getName(); if (entry.getValue().contains(compName) && !isReactants.contains(reactionName)){ ProblemInfo pi = new ProblemInfo("The component " + compName + " affects the rate of the reaction " + reactionName + " but is not a reactant", // Not sure where to report the error // either at the rate definition or the // component definition, we currently opt // for the latter because then multiple such // warning are in the correct order (since // here we are iterating through the component // definitions). compdata); pi.severity = Severity.WARNING; problems.add(pi); } } } } /* * Given a component definition return all the reactions in which * the component is a reactant. */ private HashSet<String> componentReactantReactions(ComponentData cd){ HashSet<String> reactions = new HashSet<String> (); for (PrefixData prefix : cd.getPrefixes()){ if (prefix instanceof ActionData){ ActionData actionData = (ActionData) prefix; /* * I think this is true even if it is an enzyme, but check? * And also for general modifier and inhibitor. */ Operator operator = actionData.getOperator(); if (operator.equals(Operator.REACTANT) || operator.equals(Operator.ACTIVATOR) || operator.equals(Operator.INHIBITOR) || operator.equals(Operator.GENERIC) ){ reactions.add(actionData.getFunction()); } } } return reactions; } private class SystemEquationVisitor { void visit(SystemEquationNode node) { if (node instanceof ComponentNode) visit((ComponentNode) node); else if (node instanceof CooperationNode) { // Could have been placed in its own call but seems a little // over the top visit(((CooperationNode) node).getLeft()); visit(((CooperationNode) node).getRight()); } else throw new IllegalArgumentException("Unrecognised subclass of SystemEquationNode."); } void visit(ComponentNode node) { ComponentData cd = compiledModel.getComponentData(node.getComponent()); ActionData ad; FunctionalRateData frd; CompartmentData compartmentData = node.getCompartment(); ProblemInfo pi; for (PrefixData pd : cd.getPrefixes()) { if (pd instanceof ActionData) { ad = (ActionData) pd; if (ad.getOperator().equals(Operator.REACTANT)) { frd = compiledModel.getFunctionalRate(pd.getFunction()); /* * Importantly we must check for null because * it could be that the rate function isn't defined * at all which will be caught by an earlier check * but this check is still performed. */ if (frd == null){ /* * We actually don't need to report this since * it should be else where reported */ pi = new ProblemInfo ("The rate " + pd.getFunction() + " is not defined but used in " + node.toString(),pd); problems.add(pi); } else if (!frd.isPredefinedLaw()) { if (ad.getLocations().size() == 0 || compartmentData == null || ad.getLocations().contains(compartmentData.getName())) { rrp.reactant = node.getName(); if (!frd.getRightHandSide().accept(rrp)) { pi = new ProblemInfo("The rate " + pd.getFunction() + " does not rely on " + rrp.reactant + ". Population count could decrease below zero.", pd); pi.severity = Severity.WARNING; problems.add(pi); } } } } } } } } private class ReactantParticipantVisitor extends CompiledExpressionVisitor { String reactant; @Override public boolean visit(CompiledDynamicComponent component) { return component.getName().equals(reactant); } @Override public boolean visit(CompiledFunction function) { boolean found = false; for (CompiledExpression ce : function.getArguments()) found = found || ce.accept(this); return found; } @Override public boolean visit(CompiledNumber number) { return false; } @Override public boolean visit(CompiledOperatorNode operator) { /* * We'd prefer to do the simple thing of just * descending into both expressions however either * may be null. If indeed they are null then it is * likely due to an error which should be detected * earlier. */ CompiledExpression left = operator.getLeft(); CompiledExpression right = operator.getRight(); boolean leftBool = false ; boolean rightBool = false ; if (left != null){ leftBool = left.accept(this); } if (right != null){ rightBool = right.accept(this); } return (leftBool || rightBool); } @Override public boolean visit(CompiledSystemVariable variable) { return false; } } /* * In contrast to the class above: ReactantParticipantVisitor * this visitor builds up a list of reactants which are mentioned * in the functional rate. This is in order to implement the check * "Does a reaction's rate depend on the population of a variable * which is not involved in the reaction". Therefore we can safely * ignore the predefined rate laws. This means that you cannot use * this visitor for other means, because it will not return all * the species populations which affect the rate since it won't * return all the reactants when faced with a predefined law * such as 'fMA'. In particular it cannot be used to implement * the above, although in theory we could implement them both at * the same time. */ private class ExternalParticipationVisitor extends CompiledExpressionVisitor { HashSet <String> participants; ExternalParticipationVisitor (){ this.participants = new HashSet<String>(); } public HashSet<String> getParticipants(){ return this.participants; } @Override public boolean visit(CompiledDynamicComponent component) { participants.add(component.getName()); return false; } @Override public boolean visit(CompiledFunction function) { for (CompiledExpression ce : function.getArguments()){ ce.accept(this); } return false; } @Override public boolean visit(CompiledNumber number) { return false; } @Override public boolean visit(CompiledOperatorNode operator) { /* * We'd prefer to do the simple thing of just * descending into both expressions however either * may be null. If indeed they are null then it is * likely due to an error which should be detected * earlier. */ CompiledExpression left = operator.getLeft(); CompiledExpression right = operator.getRight(); if (left != null) { left.accept(this); } if (right != null) { right.accept(this); } return false; } @Override public boolean visit(CompiledSystemVariable variable) { return false; } } }