package uk.ac.ed.inf.biopepa.core.sba;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import uk.ac.ed.inf.biopepa.core.BioPEPAException;
import uk.ac.ed.inf.biopepa.core.compiler.CompartmentData;
import uk.ac.ed.inf.biopepa.core.compiler.CompiledExpression;
import uk.ac.ed.inf.biopepa.core.compiler.ComponentNode;
/*
* This class implements the extraction of a sub-model from
* the larger model. We're working over the sba of the model.
*/
public class ModuleExtractor {
private SBAModel sbaModel;
private Map<String, Number> components;
/*
* The idea is that we can allow multiple module extractions
* but that each reaction must only go into exactly one module.
*/
private Set<SBAReaction> seen_reactions;
public ModuleExtractor (SBAModel sbaModel,
Map<String, Number> subModuleComps){
this.sbaModel = sbaModel;
this.components = subModuleComps;
this.seen_reactions = new HashSet<SBAReaction>();
}
public void reset_seen_reactions(){
this.seen_reactions.clear();
}
private class ProcessDef {
private String name ;
// A mapping from reaction names to the behaviour of this component
// within that reaction.
HashMap<String, SBAComponentBehaviour> rmap;
public ProcessDef (String name){
this.name = name;
this.rmap = new HashMap<String, SBAComponentBehaviour>();
}
public String getName (){
return this.name;
}
public void add_reaction_behaviour(String reaction,
SBAComponentBehaviour behaviour){
rmap.put(reaction, behaviour);
}
public void pretty_print (StringConsumer sc) throws IOException{
sc.append(this.name);
sc.append(" = ");
boolean first = true;
for (Entry<String, SBAComponentBehaviour> entry : rmap.entrySet()){
if (!first){ sc.append(" + "); } else { first = false; }
String rName = entry.getKey();
SBAComponentBehaviour sbaBehave = entry.getValue();
String behaviour = sbaBehave.format(rName);
sc.append(behaviour);
}
sc.appendLine(" ;");
}
}
private class ReactionDef {
private String name ;
private CompiledExpression rate;
public ReactionDef (String name, CompiledExpression rate){
this.name = name;
this.rate = rate.returnExpandedIfPresent();
}
public void pretty_print(StringConsumer sc) throws IOException{
sc.append(this.name);
sc.append(" = [");
sc.append(rate.toString());
sc.appendLine("];");
}
}
private class LocationDef {
private String name;
private CompartmentData location;
public LocationDef (String name, CompartmentData l){
this.name = name;
this.location = l;
}
public String getName(){
return this.name;
}
public void pretty_print(StringConsumer sc) throws IOException {
String parent = location.getParent();
double volume = location.getVolume();
double step_size = location.getStepSize();
sc.append("location ");
sc.append(this.name);
if (parent != null && !parent.equals("")){
sc.append(" in ");
sc.append(parent);
}
sc.append(" : ");
if (!Double.isNaN(volume)){
sc.append("size = ");
sc.append(Double.toString(volume));
sc.append(", ");
}
if (!Double.isNaN(step_size)){
sc.append("step-size = ");
sc.append(Double.toString(step_size));
sc.append(", ");
}
/*
* Note we have type last here as this can always be output
* and hence we do not need to worry about the above possibly
* leaving a trailing comma separator.
*/
sc.append("type=");
sc.append(this.location.getType().format());
sc.append(" ;");
/* small note, upper and lower properties are accepted by
* the parser, but seem otherwise unimplemented, in particular
* in CompartmentData.setProperty an exception will be raised.
* Hence we just ignore any possibility of them here.
*/
}
}
private class SourcelessModel {
LinkedList<ProcessDef> processdefs;
LinkedList<ReactionDef> reactiondefs;
LinkedList<LocationDef> locationdefs;
// A mapping from component names to initial populations
HashMap<String, Number> initPops;
public SourcelessModel(){
this.processdefs = new LinkedList<ProcessDef>();
this.reactiondefs = new LinkedList<ReactionDef>();
this.locationdefs = new LinkedList<LocationDef>();
this.initPops = new HashMap<String, Number>();
}
/*
* Return the process definition associated with the given component
* name. This may result in the addition of a new process definition
* if no appropriate process definition exists.
* Note that this is not the simple case of looking to see if the
* given name is the same, we must check if the given name is a
* located name, and if so check only for the non-located part.
*/
public ProcessDef get_process_definition(String comp){
for (ProcessDef pd : this.processdefs){
if (pd.getName().equals(comp)){
return pd;
}
}
// If we get this far then no current process definition is
// associated with the given name.
ProcessDef newpd = new ProcessDef(comp);
this.processdefs.addLast(newpd);
return newpd;
}
public void add_reaction_def(ReactionDef rd){
this.reactiondefs.addLast(rd);
}
public void add_initial_population(String comp, Number pop){
this.initPops.put(comp, pop);
}
public void add_location_definition(CompartmentData location){
// Loop through the current location definitions and see
// if we have already added a location with the given name.
String name = location.getName();
for (LocationDef ldef : this.locationdefs){
if (ldef.getName().equals(name)){
return ;
}
}
// If there is no such location definition already then we
// simply add a new one.
LocationDef newld = new LocationDef(name, location);
this.locationdefs.addLast(newld);
}
public void pretty_print (StringConsumer sc) throws IOException{
sc.appendLine("// Location definitions");
for (LocationDef ldef : this.locationdefs){
ldef.pretty_print(sc);
}
sc.endLine();
sc.appendLine("// Reaction rate definitions");
for (ReactionDef rdef : reactiondefs){
rdef.pretty_print(sc);
}
sc.endLine();
sc.appendLine("// Process definitions");
for (ProcessDef pdef : processdefs){
pdef.pretty_print(sc);
}
sc.endLine();
sc.appendLine("// System equation");
boolean first = true;
for (Entry<String, Number> entry : initPops.entrySet()){
if (!first){ sc.appendLine("<*>"); } else { first = false; }
sc.append(entry.getKey());
sc.append("[");
sc.append(entry.getValue().toString());
sc.append("]");
}
}
}
public void extract (StringConsumer sc)
throws IOException, BioPEPAException{
SourcelessModel smodel = new SourcelessModel();
SBAReaction[] allReactions = sbaModel.getReactions();
LinkedList<SBAReaction> reactions = new LinkedList<SBAReaction>();
// Figure out the set of reactions we are going to keep.
for (SBAReaction reaction : allReactions){
// Basically the reaction can stay if nobody outside
// of components affects the rate of the reaction.
// So first obtain all the components which affect the
// rate of the current reaction
// If the resultant set is now empty we know that there
// are no components which affect the rate of this reaction
// that we do not intend to keep, hence we can keep the reaction.
// Of course we also hope require that a reaction be in at most one
// module, hence if we have already seen the reaction then we shouldn't
// add it here.
if (this.seen_reactions.contains(reaction)){
continue;
}
Set<String> rateAffectors =
AnalysisUtils.reactionRateModifiers(reaction);
// Remove from that set all those components which
// are in the set of components we wish to keep
for (String component : this.components.keySet()){
rateAffectors.remove(component);
}
if (rateAffectors.isEmpty()){
reactions.addLast(reaction);
}
}
for (SBAReaction reaction : reactions){
ReactionDef rd = new ReactionDef(reaction.getName(),
reaction.getRate());
smodel.add_reaction_def(rd);
// We also add it to her self contained list of seen_reactions
// in this way we can avoid exporting a reaction in two different
// modules.
this.seen_reactions.add(reaction);
}
for (Entry<String, Number> entry : this.components.entrySet()){
String comp = entry.getKey();
ComponentNode compNode = sbaModel.getNamedComponent(comp);
// If there is no such node we should probably raise an exception
if (compNode == null){
String message = "Could not find component: " + comp;
throw new BioPEPAException (message);
}
// To obtain the correct process definition we must give
// the unlocalised name of the component which may be different
// from the localised name. For example if the component name is
// A@l then we want the process definition for just A.
String unlocalised = compNode.getComponent();
ProcessDef pd = smodel.get_process_definition(unlocalised);
// Add stuff to the definition
for (SBAReaction reaction : reactions){
SBAComponentBehaviour cb =
AnalysisUtils.involvedBehaviour(comp, reaction);
if (cb != null){
String rname = reaction.getName();
pd.add_reaction_behaviour(rname, cb);
}
}
// In addition to adding the process definition we must add
// the initial concentration.
smodel.add_initial_population(comp, entry.getValue());
// And also we must add the definition of the location
// unless it has already been added or we are not dealing with
// a localised name:
CompartmentData location = compNode.getCompartment();
if (location != null){
smodel.add_location_definition(location);
}
}
smodel.pretty_print(sc);
}
}