package com.plectix.simulator.staticanalysis;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import com.plectix.simulator.interfaces.ConnectedComponentInterface;
import com.plectix.simulator.interfaces.ObservableConnectedComponentInterface;
import com.plectix.simulator.interfaces.SolutionInterface;
import com.plectix.simulator.simulationclasses.action.Action;
import com.plectix.simulator.simulationclasses.action.ActionObserverInteface;
import com.plectix.simulator.simulationclasses.action.ActionType;
import com.plectix.simulator.simulationclasses.action.AddAction;
import com.plectix.simulator.simulationclasses.action.DefaultAction;
import com.plectix.simulator.simulationclasses.action.DeleteAction;
import com.plectix.simulator.simulationclasses.injections.Injection;
import com.plectix.simulator.simulationclasses.probability.WeightedItem;
import com.plectix.simulator.simulationclasses.solution.RuleApplicationPoolInterface;
import com.plectix.simulator.simulator.SimulationData;
import com.plectix.simulator.simulator.ThreadLocalData;
import com.plectix.simulator.staticanalysis.stories.storage.EventBuilder;
import com.plectix.simulator.staticanalysis.stories.storage.NullEvent;
import com.plectix.simulator.staticanalysis.stories.storage.StoryStorageException;
import com.plectix.simulator.util.io.PlxLogger;
/**
* This class is an implementation of 'rule' entity.
*
* <br>
* <br>
* Rule has two handsides with list of connected components each, name and rate.
*
* <br>
* In general we have kappa file line like <br>
* <br>
* <code>'ruleName' leftHandSide -> rightHandSide @ ruleRate</code>, where <br>
* <li><code>ruleName</code> - name of this rule</li> <li>
* <code>leftHandside</code> - list of substances (maybe empty)</li> <li>
* <code>rightHandside</code> - list of substances (maybe empty), which will
* replace leftHandside after applying this rule</li> <li><code>ruleRate</code>
* - rate of this rule, i.e. number, which affects on rule application frequency
* in a whole process</li> <br>
* <br>
* For example, we have kappa file line such as : <code>'name' A(x) -> B(), C() @ 0.0</code> <br>
* This one means rule, which transform agent A with site x to agents B and C.
* Notice that this rule will never be applied, because it has zero
* <code>ruleRate</code>
*
* @see ConnectedComponent
* @author evlasov
*/
public class Rule implements WeightedItem {
private static final PlxLogger LOGGER = ThreadLocalData
.getLogger(Rule.class);
private final List<ConnectedComponentInterface> leftHandside;
private final List<ConnectedComponentInterface> rightHandside;
private final String ruleName;
private List<Site> sitesConnectedWithDeleted;
private List<Site> sitesConnectedWithBroken;
private boolean doesNothing;
private int automorphismNumber = 1;
private boolean hasInfiniteRate = false;
private final List<Rule> activatedRules = new LinkedList<Rule>();
private final List<Rule> inhibitedRule = new LinkedList<Rule>();
private final List<ObservableConnectedComponentInterface> activatedObservable = new LinkedList<ObservableConnectedComponentInterface>();
private final List<ObservableConnectedComponentInterface> inhibitedObservable = new LinkedList<ObservableConnectedComponentInterface>();
private int ruleId;
private List<Action> actionList;
private Map<Agent, Agent> agentAddList;
private List<Injection> injections;
private List<Site> changedActivatedSites;
private List<ChangedSite> changedInhibitedSites;
private double activity = 0.;
private double rate;
private static NullEvent nullEvent = new NullEvent();
private RuleApplicationPoolInterface pool;
/**
* This one is and additional rate, that we should consider when applying a
* binary rule
*/
private double additionalRate = -1;
private final boolean isBinary;
/**
* The only CRule constructor.
*
* @param leftHandsideComponents
* left handside of the rule, list of connected components
* @param rightHandsideComponents
* right handside of the rule, list of connected components
* @param ruleName
* name of the rule
* @param ruleRate
* rate of the rule
* @param ruleId
* unique rule identificator
* @param isStorify
* <tt>true</tt> if simulator run in storify mode, <tt>false</tt>
* otherwise
*/
public Rule(List<ConnectedComponentInterface> leftHandsideComponents,
List<ConnectedComponentInterface> rightHandsideComponents,
String ruleName, double ruleRate, int ruleId, boolean isStorify) {
if (leftHandsideComponents == null) {
leftHandside = new ArrayList<ConnectedComponentInterface>();
leftHandside.add(ThreadLocalData.getEmptyConnectedComponent());
} else {
this.leftHandside = leftHandsideComponents;
}
this.rightHandside = rightHandsideComponents;
this.rate = ruleRate;
associateWithComponents(leftHandsideComponents);
associateWithComponents(rightHandsideComponents);
for (ConnectedComponentInterface cc : this.leftHandside) {
cc.initSpanningTreeMap();
}
if (ruleRate == Double.POSITIVE_INFINITY) {
this.hasInfiniteRate = true;
this.rate = 1;
} else {
this.rate = ruleRate;
}
this.ruleName = ruleName;
this.ruleId = ruleId;
calculateAutomorphismsNumber();
markRightHandSideAgents();
createActionList();
sortActionList();
if (isStorify) {
doesNothing = true;
for (Action action : actionList) {
if (action.getType() != ActionType.NONE) {
doesNothing = false;
break;
}
}
}
isBinary = (leftHandside.size() == 2)
&& (leftHandside.get(0).getAgents().size() == 1)
&& (leftHandside.get(1).getAgents().size() == 1)
&& onlyOneBoundAction();
}
private final boolean onlyOneBoundAction() {
int counter = 0;
for (Action action : actionList) {
if (action.getType() == ActionType.NONE) {
continue;
}
if (action.getType() == ActionType.BOUND) {
counter++;
} else {
return false;
}
if (counter > 2) {
return false;
}
}
return true;
}
/**
* This method links every connected component in given list with this rule
*
* @param connectedComponents
* give list of connected component
*/
private final void associateWithComponents(
List<ConnectedComponentInterface> connectedComponents) {
if (connectedComponents == null)
return;
for (ConnectedComponentInterface cc : connectedComponents)
cc.setRule(this);
}
/**
*
* @return <tt>true</tt> if left handside of this rule contains no
* substances, otherwise <tt>false</tt>
*/
final boolean leftHandSideIsEmpty() {
return leftHandside.contains(ThreadLocalData
.getEmptyConnectedComponent());
}
/**
*
* @return <tt>true</tt> if left handside of this rule contains the same
* substances as right handside, otherwise <tt>false</tt>
*/
public final boolean doesNothing() {
return doesNothing;
}
/**
* Indicates if rate of this rule is infinite
*
* @return <tt>true</tt> if rate of this rule is infinite, otherwise
* <tt>false</tt>
*/
public final boolean hasInfiniteRate() {
return hasInfiniteRate;
}
/**
* Sets rate of this rule to infinity
*
* @param infiniteRate
*/
public final void setInfinityRateFlag(boolean infiniteRate) {
this.hasInfiniteRate = infiniteRate;
}
/**
* This method is used by simulator in "storify" mode to apply current rule
*
* @param injections
* list of injections, which point to substances this rule will
* be applied to
* @param netNotation
* INetworkNotation object which keep information about rule
* application
* @param simulationData
* simulation data
* @param lastApplication
* is <tt>true</tt> if and only if this application is the latest
* in current simulation, otherwise false
* @throws StoryStorageException
*/
public void applyRuleForStories(List<Injection> injections,
EventBuilder eventBuilder, SimulationData simulationData,
boolean lastApplication) throws StoryStorageException {
apply(injections, eventBuilder, simulationData, lastApplication);
}
/**
* This method is used by simulator in "simulation" mode to apply current
* rule
*
* @param injectionList
* list of injections, which point to substances this rule will
* be applied to
* @param simulationData
* simulation data
* @throws StoryStorageException
*/
public void applyRule(List<Injection> injectionList,
SimulationData simulationData) throws StoryStorageException {
apply(injectionList, nullEvent, simulationData, false);
}
/**
* This method searches agent in solution, which was added with the latest
* application of this rule
*
* @param agent
* agent from the right handside of the rule, which was "parent"
* of the unknown agent
* @return agent in solution, which was added with the latest application of
* this rule
*/
public final Agent getAgentAdd(Agent agent) {
return agentAddList.get(agent);
}
/**
* This method puts agent in solution, which was added with the latest
* application of this rule, with it's "parent" - agent from the right
* handside of the rule
*
* @param agent
* agent from the right handside of the rule
* @param agentFromSolution
* agent from solution
*/
public final void registerAddedAgent(Agent agent, Agent agentFromSolution) {
agentAddList.put(agent, agentFromSolution);
}
/**
* The main method for the rule application
*
* @param injections
* list of injections, which point to substances this rule will
* be applied to
* @param netNotation
* INetworkNotation object which keep information about rule
* application
* @param simulationData
* simulation data
* @param lastApplication
* is <tt>true</tt> if and only if this application is the latest
* in current simulation, otherwise false
* @throws StoryStorageException
*/
protected final void apply(List<Injection> injections,
ActionObserverInteface event, SimulationData simulationData,
boolean lastApplication) throws StoryStorageException {
agentAddList = new LinkedHashMap<Agent, Agent>();
sitesConnectedWithDeleted = new ArrayList<Site>();
sitesConnectedWithBroken = new ArrayList<Site>();
this.injections = injections;
if (rightHandside != null) {
for (ConnectedComponentInterface cc : rightHandside) {
cc.clearAgentsFromSolutionForRHS();
}
}
event.setTypeById(simulationData.getStoriesAgentTypesStorage());
for (Action action : actionList) {
if (action.getLeftCComponent() == null) {
action.doAction(pool, null, event, simulationData);
} else {
action.doAction(pool, injections.get(leftHandside
.indexOf(action.getLeftCComponent())), event,
simulationData);
}
}
}
public final RuleApplicationPoolInterface getPool() {
return pool;
}
public final void preparePool(SimulationData simulationData) {
SolutionInterface solution = simulationData.getKappaSystem()
.getSolution();
pool = solution.prepareRuleApplicationPool();
}
/**
* This method sets idInRuleSide parameters to the agents from the rule's
* right handside and needed for initialization
*/
private final void markRightHandSideAgents() {
List<Agent> rhsAgents = new ArrayList<Agent>();
List<Agent> lhsAgents = new ArrayList<Agent>();
if (leftHandside.get(0).isEmpty()) {
int indexAgentRHS = 0;
for (ConnectedComponentInterface cc : rightHandside)
for (Agent agent : cc.getAgents())
if (agent.getIdInRuleHandside() == Agent.UNMARKED)
agent.setIdInRuleSide(indexAgentRHS++);
return;
} else {
for (ConnectedComponentInterface cc : leftHandside) {
lhsAgents.addAll(cc.getAgents());
}
}
if (rightHandside == null)
return;
for (ConnectedComponentInterface cc : rightHandside) {
rhsAgents.addAll(cc.getAgents());
}
sortAgentsByIdInRuleHandside(rhsAgents);
sortAgentsByIdInRuleHandside(lhsAgents);
int index = 0;
for (Agent lhsAgent : lhsAgents) {
if ((index < rhsAgents.size())
&& !(rhsAgents.get(index).equalz(lhsAgent) && rhsAgents
.get(index).siteMapsAreEqual(lhsAgent))) {
break;
}
// filling of fixed agents
// if (index < rhsAgents.size() && isStorify)
// fillFixedSites(lhsAgent, rhsAgents.get(index));
index++;
}
for (int i = index; i < rhsAgents.size(); i++) {
if (index == 0)
rhsAgents.get(i).setIdInRuleSide(lhsAgents.size() + i + 1);
else
rhsAgents.get(i).setIdInRuleSide(lhsAgents.size() + i);
}
}
/**
* This method sorts agents by id in rule's handside
*
* @param agents
* list of agents to be sorted
*/
private final void sortAgentsByIdInRuleHandside(List<Agent> agents) {
Agent left;
Agent right;
for (int i = 0; i < agents.size() - 1; i++) {
for (int j = i + 1; j < agents.size(); j++) {
left = agents.get(i);
right = agents.get(j);
if (left.getIdInRuleHandside() > right.getIdInRuleHandside()) {
agents.set(i, right);
agents.set(j, left);
}
}
}
}
/**
* This method sorts action of this rule by priority
*/
private final void sortActionList() {
for (int i = 0; i < actionList.size(); i++) {
for (int j = 0; j < actionList.size(); j++) {
if (actionList.get(i).getType().compareTo(
actionList.get(j).getType()) < 0) {
Action actionMin = actionList.get(j);
Action actionR = actionList.get(i);
actionList.set(j, actionR);
actionList.set(i, actionMin);
}
}
}
}
/**
* Util method using when creating actions list
*
* @param sourceSite
* @param internalState
* @param linkState
*/
public final void addInhibitedChangedSite(Site sourceSite,
boolean internalState, boolean linkState) {
for (ChangedSite inSite : changedInhibitedSites)
if (inSite.getSite() == sourceSite) {
if (!inSite.hasInternalState())
inSite.setInternalState(internalState);
if (!inSite.hasLinkState())
inSite.setLinkState(linkState);
return;
}
changedInhibitedSites.add(new ChangedSite(sourceSite, internalState,
linkState));
}
/**
* This methods creates atomic-actions list for this rule
*/
private final void createActionList() {
changedActivatedSites = new ArrayList<Site>();
changedInhibitedSites = new ArrayList<ChangedSite>();
actionList = new ArrayList<Action>();
if (rightHandside != null)
for (ConnectedComponentInterface cc : rightHandside)
for (Agent agentRight : cc.getAgents())
for (Site site : agentRight.getSites()) {
changedActivatedSites.add(site);
}
if (rightHandside == null) {
for (ConnectedComponentInterface ccL : leftHandside)
for (Agent lAgent : ccL.getAgents()) {
actionList.add(new DeleteAction(this, lAgent, ccL));
for (Site site : lAgent.getSites()) {
changedInhibitedSites.add(new ChangedSite(site, true,
true));
}
}
return;
}
int lhsAgentsQuantity = 0;
if (!this.leftHandSideIsEmpty()) {
for (ConnectedComponentInterface cc : leftHandside) {
lhsAgentsQuantity += cc.getAgents().size();
}
}
for (ConnectedComponentInterface ccR : rightHandside) {
for (Agent rAgent : ccR.getAgents()) {
if ((lhsAgentsQuantity == 0)
|| (rAgent.getIdInRuleHandside() > lhsAgentsQuantity)) {
actionList.add(new AddAction(this, rAgent, ccR));
// fillChangedSites(rAgent);
}
}
}
if (leftHandside.get(0).isEmpty())
return;
for (ConnectedComponentInterface ccL : leftHandside)
for (Agent lAgent : ccL.getAgents()) {
this.addAllActions(lAgent, ccL);
}
}
/**
* This method adds all actions among to a given agent
*
* @param lAgent
* given agent
* @param ccL
* connected component which contains lAgent
*/
private final void addAllActions(Agent lAgent,
ConnectedComponentInterface ccL) {
for (ConnectedComponentInterface ccR : rightHandside) {
for (Agent rAgent : ccR.getAgents()) {
if (lAgent.getIdInRuleHandside() == rAgent
.getIdInRuleHandside()) {
Action newAction = new DefaultAction(this, lAgent, rAgent,
ccL, ccR);
actionList.add(newAction);
actionList.addAll(newAction.createAtomicActions());
return;
}
}
}
actionList.add(new DeleteAction(this, lAgent, ccL));
}
/**
* Calculates automorphism number for this rule
*/
private final void calculateAutomorphismsNumber() {
if (leftHandside != null) {
Map<String, Integer> repetitionFactor = new LinkedHashMap<String, Integer>();
for (ConnectedComponentInterface component : leftHandside) {
String hashForComponent = component.getSmilesString();
Integer old = repetitionFactor.get(hashForComponent);
if (old == null) {
repetitionFactor.put(hashForComponent, Integer.valueOf(1));
} else {
repetitionFactor.put(hashForComponent, Integer
.valueOf(old + 1));
}
}
for (Integer i : repetitionFactor.values()) {
automorphismNumber *= factorial(i);
}
}
}
private int factorial(int i) {
if (i == 0) {
throw new RuntimeException(
"internal error : repetition factor for component equals 0");
}
int answer = 1;
for (int j = 1; j <= i; j++) {
answer *= j;
}
return answer;
}
/**
* This method calculates activity of this rule according to it's current
* parameters
*/
public final void calculateActivity() {
activity = 1.;
for (ConnectedComponentInterface cc : this.leftHandside) {
activity *= cc.getInjectionsWeight();
}
if (!this.bolognaWanted()) {
activity *= rate;
} else {
double k1 = rate;
// additional rate
double k2 = additionalRate;
long commonInjectionsWeight = 0;
for (ConnectedComponentInterface cc : this.leftHandside) {
commonInjectionsWeight += cc.getInjectionsWeight();
}
double temp = k1 / commonInjectionsWeight;
double k2prime = Math.max(temp, k2);
activity *= k2prime;
}
activity /= automorphismNumber;
}
/**
* This method returns the name of this rule
*
* @return the name of this rule
*/
public final String getName() {
return ruleName;
}
public int getAutomorphismNumber() {
return automorphismNumber;
}
/**
* This method returns activity of this rule
*
* @return activity of this rule
*/
public final double getActivity() {
return activity;
}
/**
* This method sets activity of this rule to a given value
*
* @param activity
* new value
*/
public final void setActivity(double activity) {
this.activity = activity;
}
/**
* This method returns list of components from the left handside of this
* rule
*
* @return list of components from the left handside of this rule
*/
public final List<ConnectedComponentInterface> getLeftHandSide() {
if (leftHandside == null) {
return null;
} else {
return leftHandside;
}
}
/**
* This method returns list of components from the right handside of this
* rule
*
* @return list of components from the right handside of this rule
*/
public final List<ConnectedComponentInterface> getRightHandSide() {
if (rightHandside == null) {
return null;
} else {
return rightHandside;
}
}
/**
* This method returns injection from the injection-list from the latest
* application of this rule, which points to connected component, including
* siteTo
*
* @param siteTo
* given site
* @return injection from the injection-list from the latest application of
* this rule, which points to connected component, including siteTo
*/
public final Injection getInjectionBySiteToFromLHS(Site siteTo) {
int sideId = siteTo.getParentAgent().getIdInRuleHandside();
int i = 0;
for (ConnectedComponentInterface cc : leftHandside) {
for (Agent agent : cc.getAgents())
if (agent.getIdInRuleHandside() == sideId)
return injections.get(i);
i++;
}
return null;
}
public final List<Site> getSitesConnectedWithBroken() {
return sitesConnectedWithBroken;
}
/**
* This method adds given site util list.
*
* @param site
* given site
*/
public final void addSiteConnectedWithBroken(Site site) {
sitesConnectedWithBroken.add(site);
}
public final List<Site> getSitesConnectedWithDeleted() {
return sitesConnectedWithDeleted;
}
public final void addSiteConnectedWithDeleted(Site site) {
sitesConnectedWithDeleted.add(site);
}
public final Site getSiteConnectedWithDeleted(int index) {
return sitesConnectedWithDeleted.get(index);
}
public final void removeSiteConnectedWithDeleted(int index) {
sitesConnectedWithDeleted.remove(index);
}
public final void addAction(Action action) {
actionList.add(action);
}
/**
* We use this method to compare rules
*
* @param obj
* another rule
* @return <tt>true</tt> if rules have similar id's, otherwise
* <tt>false</tt>
*/
public final boolean equalz(Rule obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
Rule rule = obj;
return rule.toString().equals(this.toString());
}
/**
* Returns id number of current rule
*
* @return rule id
*/
public final int getRuleId() {
return ruleId;
}
/**
* Sets id of current rule to a given value
*
* @param id
* new value of rule id
*/
public final void setRuleID(int id) {
this.ruleId = id;
}
/**
* Returns current rule rate
*
* @return rule rate
*/
public final double getRate() {
return rate;
}
/**
* Sets current rule rate to a given value
*
* @param ruleRate
* new value of rule rate
*/
public final void setRuleRate(double ruleRate) {
if (ruleRate >= 0) {
this.rate = ruleRate;
} else {
LOGGER.info("warning : rate of the rule '" + ruleName
+ "' was attempted to be set as negative");
this.rate = 0;
}
}
/**
* Returns list of observable components which activate by this rule
*
* @return list of observable components which activate by this rule
*/
public final List<ObservableConnectedComponentInterface> getActivatedObservable() {
return activatedObservable;
}
/**
* Returns list of rules which activate by this rule
*
* @return list of rules which activate by this rule
*/
public final List<Rule> getActivatedRules() {
return activatedRules;
}
public final void addActivatedRule(Rule rule) {
activatedRules.add(rule);
}
public final void addinhibitedRule(Rule rule) {
inhibitedRule.add(rule);
}
public final void addActivatedObs(ObservableConnectedComponentInterface obs) {
activatedObservable.add(obs);
}
public final void addinhibitedObs(ObservableConnectedComponentInterface obs) {
inhibitedObservable.add(obs);
}
/**
* Returns list of actions, which this rule performs
*
* @return list of actions, which this rule performs
*/
public final List<Action> getActionList() {
return actionList;
}
@Override
public final double getWeight() {
return activity;
}
public final void setAdditionalRate(double binaryRate) {
this.additionalRate = binaryRate;
}
public final double getAdditionalRate() {
return this.additionalRate;
}
/**
* This method tells us when we should perform Bologna method on application
* of this rule
*
* @return <tt>true</tt> if and only if this rule is binary and we should
* consider it's additional rate when applying
*/
public final boolean bolognaWanted() {
return this.isBinary && additionalRate != -1;
}
public final void positiveUpdate(List<Rule> rules,
List<ObservableConnectedComponentInterface> observables) {
for (Rule ruleFromList : rules) {
// if(rules!=rule)
for (ConnectedComponentInterface cc : ruleFromList
.getLeftHandSide()) {
cc.doPositiveUpdate(this.rightHandside);
}
}
for (ObservableConnectedComponentInterface oCC : observables) {
if (oCC.getMainAutomorphismNumber() == ObservableConnectedComponent.NO_INDEX)
oCC.doPositiveUpdate(this.rightHandside);
}
}
@Override
public final String toString() {
StringBuffer st = new StringBuffer(leftHandside.toString());
st.append("->");
if (rightHandside == null) {
st.append("[]");
} else {
st.append(rightHandside);
}
return st.toString();
}
public final String getCanonicalRuleString() {
Set<String> leftHandSideSorted = new TreeSet<String>();
if (this.getLeftHandSide() != null) {
for (ConnectedComponentInterface component : this.getLeftHandSide()) {
leftHandSideSorted.add(component.getSmilesString());
}
}
Set<String> rightHandSideSorted = new TreeSet<String>();
if (this.getRightHandSide() != null) {
for (ConnectedComponentInterface component : this.getRightHandSide()) {
rightHandSideSorted.add(component.getSmilesString());
}
}
StringBuffer st = new StringBuffer();
for (String leftComponent : leftHandSideSorted) {
st.append(leftComponent + " ");
}
st.append("->");
for (String rightComponent : rightHandSideSorted) {
st.append(" " + rightComponent);
}
return st.toString();
}
}