package org.cellocad.adaptors.eugeneadaptor;
import lombok.Getter;
import lombok.Setter;
import org.apache.log4j.Logger;
import org.cellocad.MIT.dnacompiler.*;
import org.cidarlab.eugene.Eugene;
import org.cidarlab.eugene.dom.Device;
import org.cidarlab.eugene.dom.NamedElement;
import org.cidarlab.eugene.dom.imp.container.EugeneArray;
import org.cidarlab.eugene.dom.imp.container.EugeneCollection;
import org.cidarlab.eugene.exception.EugeneException;
import org.cidarlab.eugene.util.DeviceUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
public class EugeneAdaptor {
/**
* Execute Eugene
*
* @param name_Eug_file
* .eug file path
* @param module_variants
* passed as empty ArrayList, populated within the method
* @param part_library
* Part objects added to module_variants based on part name from
* Eugene design
* @param options
* number of variants to design (nP) and output directory
*/
public void callEugene(String name_Eug_file,
ArrayList<ArrayList<Part>> module_variants,
PartLibrary part_library, Args options) {
try {
Eugene e = new Eugene();
// 'exports' directory is created but empty. Put it here:
// Eugene.ROOT_DIRECTORY = options.home + "/resources/eugene/";
EugeneCollection ec = e.executeFile(new File(options
.get_output_directory() + name_Eug_file));
EugeneArray variants = (EugeneArray) ec.get("allResults");
int n_variants = options.get_nP();
if (variants.getElements().size() < options.get_nP()) {
n_variants = variants.getElements().size();
}
for (int i = 0; i < n_variants; ++i) {
NamedElement circuit = variants.getElement(i);
if (circuit instanceof org.cidarlab.eugene.dom.Device) {
/*
* The full DNA sequence of the circuit is generated by
* Eugene:
*/
ArrayList<Part> module = new ArrayList<Part>();
int g_index = 0;
for (NamedElement gate : ((Device) circuit)
.getComponentList()) {
if (gate instanceof org.cidarlab.eugene.dom.Part) {
NamedElement part = gate;
String p_direction = "+";
Part p = new Part(part_library.get_ALL_PARTS().get(
part.getName()));
p.set_direction(p_direction);
module.add(p);
}
else if (gate instanceof org.cidarlab.eugene.dom.Device) {
String gate_name = gate.getName();
String g_direction = "+";
String o = ((Device) circuit).getOrientations(
g_index).toString();
if (o.equals("[REVERSE]")) {
g_direction = "-";
Device reverse_gate = DeviceUtils
.flipAndInvert((Device) gate);
gate = reverse_gate;
}
String egate = g_direction + gate_name;
ArrayList<Part> txn_unit = new ArrayList<Part>();
int p_index = 0;
for (NamedElement part : ((Device) gate)
.getComponentList()) {
String part_name = part.getName();
String p_direction = "+";
String op = ((Device) gate).getOrientations(
p_index).toString();
if (op.equals("[REVERSE]")) {
p_direction = "-";
}
Part p = new Part(part_library.get_ALL_PARTS()
.get(part.getName()));
p.set_direction(p_direction);
txn_unit.add(p);
p_index++;
}
module.addAll(txn_unit);
}
g_index++;
}
module_variants.add(module);
}
}
logger.info("Number of Eugene solutions "
+ variants.getElements().size());
} catch (EugeneException exception) {
exception.printStackTrace();
}
}
/**
* Infer the Device names from a rule by all tokens that are not Eugene
* keywords
*
* @param rule
* @return
*/
public ArrayList<String> getDeviceNamesFromRule(String rule) {
ArrayList<String> keywords = new ArrayList<String>();
// counting
keywords.add("CONTAINS");
keywords.add("NOTCONTAINS");
keywords.add("EXACTLY");
keywords.add("NOTEXACTLY");
keywords.add("MORETHAN");
keywords.add("NOTMORETHAN");
keywords.add("SAME_COUNT");
keywords.add("WITH");
keywords.add("NOTWITH");
keywords.add("THEN");
// positioning
keywords.add("STARTSWITH");
keywords.add("ENDSWITH");
keywords.add("AFTER");
keywords.add("ALL_AFTER");
keywords.add("SOME_AFTER");
keywords.add("BEFORE");
keywords.add("ALL_BEFORE");
keywords.add("SOME_BEFORE");
keywords.add("NEXTTO");
keywords.add("ALL_NEXTTO");
keywords.add("SOME_NEXTTO");
// pairing
keywords.add("EQUALS");
keywords.add("NOTEQUALS");
// orientation
keywords.add("ALL_FORWARD");
keywords.add("ALL_REVERSE");
keywords.add("FORWARD");
keywords.add("ALL_FORWARD");
keywords.add("REVERSE");
keywords.add("ALL_REVERSE");
keywords.add("SAME_ORIENTATION");
keywords.add("ALL_SAME_ORIENTATION");
keywords.add("ALTERNATE_ORIENTATION");
// interaction
keywords.add("REPRESSES");
keywords.add("INDUCES");
keywords.add("DRIVES");
// logic
keywords.add("NOT");
keywords.add("AND");
keywords.add("OR");
ArrayList<String> device_names = new ArrayList<String>();
ArrayList<String> tokens = Util.lineTokenizer(rule);
for (String token : tokens) {
boolean is_keyword = false;
for (String keyword : keywords) {
if (token.equalsIgnoreCase(keyword)) {
is_keyword = true;
break;
}
}
if (is_keyword == false
&& token.substring(0, 1).matches("[a-z,A-Z]")) {
device_names.add(token);
}
}
return device_names;
}
/**
* String builder generates a .eug file from a LogicCircuit and writes the
* file to disk
*
* @param gates
* @param filename
* @param part_library
* @param options
* @return
*/
public String generateEugeneFile(ArrayList<Gate> gates, String filename,
PartLibrary part_library, Args options) {
if (options.is_eugene_scars()) {
part_library.set_scars();
}
// this is the Eugene file String that will be built
String eug = "";
// only define each part type once
HashSet<String> part_type_set = new HashSet<String>();
// only define each part once (multiple instances might exist in the
// case of promtoer fan-out)
HashSet<String> part_set = new HashSet<String>();
// map used instead of ArrayList of ArrayLists to save each txn unit
HashMap<String, ArrayList<Part>> txn_units = new HashMap<>();
for (Gate g : gates) {
if(g.Regulator == null || g.Regulator.isEmpty()) {
g.Regulator = g.Name;
}
for (int i = 0; i < g.get_txn_units().size(); ++i) {
String key = "";
if (!txn_units.containsKey(g.Regulator)) {
key = g.Regulator;
} else {
key = g.Regulator + "_" + (i + 1);
}
txn_units.put(key, g.get_txn_units().get(i));
}
}
for (ArrayList<Part> txn_unit : txn_units.values()) {
// set the part types and the parts
if (options.is_eugene_dnaseq()) {
for (Part p : txn_unit) {
part_type_set.add(p.get_type());
part_set.add(p.get_type() + " " + p.get_name()
+ "(.SEQUENCE(\"" + p.get_seq() + "\"));\n");
}
}
else {
for (Part p : txn_unit) {
part_type_set.add(p.get_type());
part_set.add(p.get_type() + " " + p.get_name() + ";\n");
}
}
}
// include scars as parts and a part type
ArrayList<Part> scars = new ArrayList<Part>();
if (options.is_eugene_scars()) {
part_type_set.add("scar");
for (int i = 0; i < txn_units.size(); ++i) {
// scars.add(get_SCARS().get(i));
scars.add(part_library.get_scars().get(i));
}
// module end scar
// scars.add(get_SCARS().get(get_SCARS().size()-1));
scars.add(part_library.get_scars().get(
part_library.get_scars().size() - 1));
for (Part p : scars) {
if (options.is_eugene_dnaseq()) {
part_set.add(p.get_type() + " " + p.get_name()
+ "(.SEQUENCE(\"" + p.get_seq() + "\"));\n");
} else {
part_set.add(p.get_type() + " " + p.get_name() + ";\n");
}
}
}
// add part type definitions to eug String
for (String part_type : part_type_set) {
eug += "PartType " + part_type + ";\n";
}
eug += "\n";
// add part definitions to eug String (sorted alphabetically)
ArrayList<String> parts = new ArrayList<String>();
for (String p : part_set) {
parts.add(p);
}
Collections.sort(parts);
for (String p : parts) {
eug += p;
}
eug += "\n";
// define each gate device
for (String regulator : txn_units.keySet()) {
ArrayList<Part> txn_unit = txn_units.get(regulator);
eug += "Device " + regulator + "_device" + "(\n";
int gi = 0;
for (Part p : txn_unit) {
if (gi != 0) {
eug += ",\n";
}
if (p.get_type().equals("promoter")) {
eug += " " + p.get_type();
} else {
eug += " " + p.get_name();
}
gi++;
}
eug += "\n);\n";
}
eug += "\n";
// define rules for each gate device
for (String regulator : txn_units.keySet()) {
ArrayList<Part> txn_unit = txn_units.get(regulator);
ArrayList<String> names_in_this_device = new ArrayList<String>();
for (Part p : txn_unit) {
names_in_this_device.add(p.get_name());
}
eug += "Rule " + regulator + "_rules " + "( ON " + regulator
+ "_device" + ":\n";
int pcount = 0;
for (Part p : txn_unit) {
if (p.get_type().equals("promoter")) {
if (pcount == 0) {
eug += " CONTAINS " + p.get_name();
pcount++;
} else {
eug += " AND \n CONTAINS " + p.get_name();
pcount++;
}
}
}
eug += insertRulesFromUCF(names_in_this_device,
get_eugene_part_rules());
eug += " AND\n ALL_FORWARD\n);\n";
}
eug += "\n\n";
// design all gate device variants
for (String regulator : txn_units.keySet()) {
eug += String.format("%-15s", regulator + "_devices")
+ " = product(" + regulator + "_device" + ");\n";
}
eug += "\n";
// initialize gate device names that will be included in the circuit
// device
ArrayList<String> names_in_circuit_device = new ArrayList<String>();
for (String regulator : txn_units.keySet()) {
eug += "Device " + "gate_" + regulator + "();" + "\n";
names_in_circuit_device.add("gate_" + regulator);
}
eug += "\n";
// initialize the circuit device
eug += "Device circuit();\n\n";
// apply rules on circuit device (before specifying the circuit device)
eug += "Rule allRules( ON circuit:\n";
// exactly 1 rule for each gate device
int gi = 0;
for (String regulator : txn_units.keySet()) {
if (gi == 0) {
eug += " " + String.format("%-12s", "gate_" + regulator)
+ " EXACTLY 1";
} else {
eug += " AND \n" + " "
+ String.format("%-12s", "gate_" + regulator)
+ " EXACTLY 1";
}
gi++;
}
// add other rules specified in the UCF, if any
eug += insertRulesFromUCF(names_in_circuit_device,
get_eugene_gate_rules());
// eug += " AND \n" + " ALL_FORWARD";
// include scars in circuit device
if (options.is_eugene_scars()) {
for (Part scar : scars) {
eug += " AND \n" + " "
+ String.format("%-12s", scar.get_name())
+ " EXACTLY 1";
}
for (Part scar : scars) {
eug += " AND \n" + " FORWARD " + scar.get_name();
}
for (int i = 0; i < scars.size(); ++i) {
eug += " AND \n" + " [" + (i * 2) + "] EQUALS "
+ scars.get(i).get_name();
}
}
eug += "\n);";
eug += "\n\n";
// initialize master array of all results
eug += "Array allResults;\n\n";
// build nested For-loops of enumerated gate devices
// the 'permute' function will be called in the inner-most loop, and
// results will be appended to allResults
int tu_counter = 0;
for (String regulator : txn_units.keySet()) {
tu_counter++;
String index = "i" + Integer.toString(tu_counter);
eug += "for(num "
+ index
+ "=0; "
+ String.format("%-28s", index + "<sizeof(" + regulator
+ "_devices);") + index + "=" + index + "+1) {\n";
}
eug += "\n";
tu_counter = 0;
for (String regulator : txn_units.keySet()) {
tu_counter++;
String index = "i" + Integer.toString(tu_counter);
// set each gate device from the prior enumeration ('product'
// function above)
eug += String.format("%-12s", "gate_" + regulator) + " = "
+ regulator + "_devices[" + index + "];\n";
}
eug += "\n";
// define the components of the circuit device... order/orientation will
// be permuted.
gi = 0;
eug += "Device circuit(\n";
for (String regulator : txn_units.keySet()) {
if (gi == 0) {
eug += " " + "gate_" + regulator;
gi++;
} else {
eug += ",\n " + "gate_" + regulator;
}
}
if (options.is_eugene_scars()) {
for (Part scar : scars) {
eug += ",\n " + scar.get_name();
}
}
eug += "\n);\n";
eug += "\n";
// permute order/orientation of gate devices within the circuit device
eug += "result = permute(circuit);\n\n";
/*
* eug += "if(sizeof(result) >500) {\n" +
* " for(num ir=0; ir<500; ir=ir+1) {\n" +
* " allResults = allResults + result[ir];\n" + " }\n" + "}\n" +
* "else {\n" + " allResults = allResults + result;\n" + "}\n\n";
*/
// append results to allResults
eug += "allResults = allResults + result;\n\n";
for (int i = 0; i < txn_units.size(); ++i) {
eug += "}\n";
}
// eug += "\n\nprintln(sizeof(allResults));\n";
logger.info(eug);
// write the file to disk
Util.fileWriter(options.get_output_directory() + filename, eug, false);
return eug;
}
private String insertRulesFromUCF(ArrayList<String> names_in_this_device,
ArrayList<String> all_rules) {
String rules = "";
if (all_rules == null) {
return "";
}
for (String rule : all_rules) {
ArrayList<String> names_in_rule = getDeviceNamesFromRule(rule);
boolean all_matching = true;
boolean inconsistent_rules = false;
for (String name : names_in_rule) {
if (!names_in_this_device.contains(name)) {
all_matching = false;
break;
}
// startswith is used to implement roadblocking. More than 1
// startswith rule for a gate device will lead to invalid Eugene
// rules
if (rules.contains("STARTSWITH")
|| rules.contains("startswith")) {
if (rule.contains("STARTSWITH")
|| rule.contains("startswith")) {
logger.info("more than 1 STARTSWITH rules found, omitting rule: "
+ rule);
inconsistent_rules = true;
}
}
}
// all rules are joined using AND
if (all_matching && !inconsistent_rules) {
rules += " AND \n " + rule;
}
}
return rules;
}
@Getter
@Setter
private ArrayList<String> _eugene_part_rules = new ArrayList<String>();
@Getter
@Setter
private ArrayList<String> _eugene_gate_rules = new ArrayList<String>();
@Getter @Setter private String threadDependentLoggername;
private Logger logger = Logger.getLogger(getClass());
}