2. SCRIPT
2.7. Organização do Script
else:
print("Option unavailable. Please Run the script again with an option contained in the menu.")
unavailableOption(option3)
Qualquer outro input que não esteja contido na listagem de opções possíveis será tratado como erro, enviando uma mensagem informativa ao utilizador e terminando a execução do script de seguida.
Adicionalmente foi criada a função unavailableOption() que recebe o input do utilizador como argumento, e é invocado nos casos em que o input no menu é um valor não aceite. Esta função regista a tentativa de execução como se de uma execução normal se tratasse, registando ao nível do log o erro que originou bem como as datas em que ocorreu.
#Unavailable Option Function
#Will add log information about the execution attempt.
def unavailableOption(option) :
currentDate=datetime.datetime.now()
dateEpoch = calendar.timegm(currentDate.timetuple())
logging.basicConfig(filename = "logfile_" + str(dateEpoch) + ".log", level = logging.INFO)
logging.info(str(datetime.datetime.now()) + ": Execution Started.")
logging.info(str(datetime.datetime.now()) + ": Option '" + str(option) + "' unavailable.
")
logging.info(str(datetime.datetime.now()) + ": Unable to run script.") logging.info(str(datetime.datetime.now()) + ": Execution Ended.")
Durante a seleção de um determinado tipo de interação para ser analisado, é feita a chamada de uma função diferente. A demonstração da lógica criada será feita mediante da apresentação de um caso específico. Foi selecionada a função responsável para as interações entre metais e o grupo amina (x-HN) (restante código na íntegra contido no capítulo 7 dos anexos). A functionFList(input1,input2) foi selecionada por ser a mais complexa e assim poder ser dado uma overview da lógica. Esta função tem 2 argumentos de entrada e durante a execução recebe as variáveis metal_res e F_amine, respetivamente.
A função inicia com a preparação do ambiente para realização da execução e criação dos ficheiros de log onde ficará registado tudo o que é realizado por parte do script. O tipo de log é definido como informativo de modo que todas as strings passadas como argumento sejam passadas para o log.
#Metals−HN Function
def functionFList(input1,input2):
count = 1
currentDate=datetime.datetime.now()
dateEpoch = calendar.timegm(currentDate.timetuple()) logFilename = "logfile_" + str(dateEpoch) + ".log"
newDirectoryName = str(dateEpoch) os.mkdir(newDirectoryName)
logging.basicConfig(filename = logFilename , level = logging.INFO) logging.info(str(datetime.datetime.now()) + ": Execution Started.")
logging.info(str(datetime.datetime.now()) + ": Option 2 Selected. Interactions Between Metal-HN.")
logging.info(str(datetime.datetime.now()) + ": Execution Directory Created '" + str(newDirectoryName) +"'.")
print ("Execution log file can be found in ('" + str(newDirectoryName) + "/" + str(logFilename) + "').")
Seguidamente é feita a preparação de todas as interações possíveis, são organizados dois arrays de dados baseados nos inputs, um com os IDs dos metais, mais precisamente do seu resíduo, e outro para os IDs dos resíduos das aminas. Este último sofre um tratamento de dados ligeiramente diferente, pois é feita a remoção de duplicados do array para evitar que sejam repetida iterações desnecessárias. Tendo por base a informação da tabela 7, em que são apresentadas as possíveis combinações de dadores e hidrogénios a partir de um determinado resíduo e sendo que na lista de valores idAmine_NoDups temos os tipos de resíduos, conseguimos facilmente identificar os hidrogénios (h_list) e os dadores (donor_list) a utilizar na iteração.
idMetal = []
for i in input1:
resid = i.resid idMetal.append(resid) idAmine = []
for i in input2:
resid = i.resid idAmine.append(resid)
idAmine_NoDups = ([item for item, count in collections.Counter(idAmine).items() if count
for i in idMetal:
for j in idAmine_NoDups:
getResid = gro.select_atoms("resid " + str(j)).residues getResid = str(getResid)
getResid = getResid.split('[<Residue ')[1]
getResid = getResid.split(',')[0]
donor_list = []
h_list = []
if (getResid == "ARG"):
donor_list = ["NH1", "NH2"]
h_list = ["HH11","HH12","HH21","HH22","HH1"]
if (getResid == "ARGN"):
donor_list = ["NH1", "NH2"]
h_list = ["HH1","HH21","HH22"]
if (getResid == "ASN"):
donor_list = ["ND2"]
h_list = ["HD21","HD22"]
if (getResid == "GLN"):
donor_list = ["NE2"]
h_list = ["HE21","HE22"]
if (getResid == "HIS"):
donor_list = ["HD1","HE2"]
h_list = ["ND1","NE2"]
if (getResid == "LYS"):
donor_list = ["HZ1","HZ2","HZ3"]
h_list = ["NZ"]
if (getResid == "LYSH"):
donor_list = ["HZ1","HZ2","HZ3"]
h_list = ["NZ"]
if (getResid == "TRP"):
donor_list = ["HE1"]
h_list = ["NE1"]
Sendo que agora estão disponíveis os IDs dos metais, dos hidrogénios e dos dadores das aminas, é agora necessário preparar as seleções diretamente no universo xtc, que contém as trajetórias e permite fazer a análise ao longo de todos os frames.
for d in donor_list:
for n in h_list:
donor_select = gro.select_atoms("name " + str(d) + " and resid " + str(j)) h_select = gro.select_atoms("name " + str(n) + " and resid " + str(j)) if len(donor_select) > 0 and len(h_select) > 0 :
donor = donor_select[0]
h = h_select[0]
xtc_metal = xtc.select_atoms("(resid " + str(i) + ")") xtc_metal_name = xtc_metal[0]
xtc_metal_name = xtc_metal_name.resname
xtc_H = xtc.select_atoms("name " + str(h.name) + " and type " + str(h.type) + "
and resid " + str(h.resid)) xtc_H_name = xtc_H[0]
xtc_donor = xtc.select_atoms("name " + str(donor.name) + " and type " + str(donor.type) + " and resid " + str(donor.resid))
xtc_donor_name = xtc_donor[0]
xtc_donor_name = xtc_donor_name.name
logging.info(str(datetime.datetime.now()) + ": --> CURRENT INTERACTION")
logging.info(str(datetime.datetime.now()) + ": Metal --> " + str(i) + " " + str(xtc_metal_name) )
logging.info(str(datetime.datetime.now()) + ": " + str(xtc_metal))
logging.info(str(datetime.datetime.now()) + ": Thiol --> " + str(j) + " " + str(xtc_H_name) + "_" + str(xtc_donor_name) )
logging.info(str(datetime.datetime.now()) + ": " + str(xtc_H) + " + " + str(xtc_donor))
arrayFramesLoop = []
arrayDistancesLoop = []
arrayAnglesLoop = []
arrayInteractionLoop = []
arrayInteractionResidLoop = []
arrayFrameDistanceAngles = []
Tendo as seleções todas previamente feitas é possível utilizar a trajectory() do ficheiro de universo .xtc para extrair a informação relativa a um determinado frame. Dito isto foi feita a iteração e a análise das posições dos metais, hidrogénios e dadores, sendo consecutivamente calculada as distâncias e os ângulos em cada frame. Todos os valores foram guardados em arrays temporários para no final do ciclo dos frames poder ser feita a média de ambos e validar se apresentam valores que sejam importantes para o estudo em curso, caso contrário serão descartados, os arrays limpos e o ciclo inicia novamente com a interação seguinte.
for ts in xtc.trajectory:
positionMetal = xtc_metal.positions positionDonor = xtc_donor.positions positionH = xtc_H.positions
centroidMetal = getCenteroid(positionMetal)
avgsDonor = [sum(vals)/len(positionDonor) for vals in zip(*positionDonor)]
avgsH = [sum(vals)/len(positionH) for vals in zip(*positionH)]
vector_H_Donor = findVec(avgsH,avgsDonor)
vector_H_Centroid = findVec(avgsH,centroidMetal)
angle=angle_between_degree(vector_H_Donor,vector_H_Centroid)
dist = math.sqrt((centroidMetal[0]-avgsDonor[0])**2 + (centroidMetal[1]-avgsDonor[1])**2 + (centroidMetal[2]-avgsDonor[2])**2 )
arrayFramesLoop.append(ts.frame) arrayDistancesLoop.append(dist) arrayAnglesLoop.append(angle)
arrayFrameDistanceAngles.append(str(xtc.trajectory.time)+ ',' + str(dist)+ ',' + str(angle))
arrayInteractionResidLoop.append(str(i) + "VS" + str(j))
arrayInteractionLoop.append(str(xtc_metal_name) + "VS" + str(xtc_H_name) + "_" + str(xtc_donor_name))
mean = numpy.mean(arrayDistancesLoop) meanAngles = numpy.mean(arrayAnglesLoop)
Feitos os cálculos da média das distâncias e dos ângulos para uma determinada interação, é feita a validação do mesmo contra os valores pré-estabelecidos e aceites como padrão para considerar importante ou não a interação. Caso se verifique que ambos os valores cumprem os requisitos, este são adicionados ao array que será responsável por criar os gráficos no final da verificação de todas as interações. Adicionalmente, para todos os casos que cumpram as condições, será escrito um ficheiro .csv com todos os valores de distâncias e ângulos para todos os frames, bem como as respetivas médias. Estes ficheiros estão identificados com um ID único e com a o nome da interação para facilitar a sua análise e consulta.
if (mean < maxMeanDistanceTest) and (meanAngles > minMeanAnglesTest) and (mean != 0.0) : logging.info(str(datetime.datetime.now()) + ": Distance Mean (" + str(mean) + ")") logging.info(str(datetime.datetime.now()) + ": Angles Mean (" + str(meanAngles) + ")") logging.info(str(datetime.datetime.now()) + ": Values will be added to the plot.") arrayFrames.append(arrayFramesLoop)
arrayDistances.append(arrayDistancesLoop) arrayAngles.append(arrayAnglesLoop)
arrayInteraction.append(arrayInteractionLoop)
arrayInteractionResid.append(arrayInteractionResidLoop)
csvfile = str(count)+ "_" + str(arrayInteractionResidLoop[0]) + "_" + str(dateEpoch) +".csv"
interaction = str(i) + str(xtc_metal_name) + "_" + str(j) + str(xtc_H_name) + "_" + str(xtc_donor_name)
arrayAllResults.append(str(interaction) + ";" + str(mean) + ";" + str(meanAngles)) csvresults = "results.csv"
arrayPlotNameDistance.append(str(i) + str(xtc_metal_name) + "_" + str(j) + str(xtc_H_name) + "_" + str(xtc_donor_name))
arrayPlotNameAngle.append(str(i) + str(xtc_metal_name) + "_" + str(j) + str(xtc_H_name) + "_" + str(xtc_donor_name))
with open(csvfile, "w") as fp:
wr = csv.writer(fp, delimiter='\t',lineterminator='\n') wr.writerow(['Avarage Distance', mean])
wr.writerow(['Interaction', arrayInteractionLoop[0]]) wr.writerow(['Frame','Distance','Angle'])
for x in arrayFrameDistanceAngles : wr.writerow ([x])
shutil.move(csvfile, newDirectoryName) count += 1
#logging.info(str(datetime.datetime.now()) + ": File Created -> " + str(csvfile)) logging.info(str(datetime.datetime.now()) + ": File '" + str(csvfile) +"' Created on Execution Directory (" + str(newDirectoryName) +").")
else:
#print("MEAN >= 10 --> Will be ignored")
logging.info(str(datetime.datetime.now()) + ": Distance Mean (" + str(mean) + ")") logging.info(str(datetime.datetime.now()) + ": Angles Mean (" + str(meanAngles) + ")") logging.info(str(datetime.datetime.now()) + ": Values Will be ignored.")
#arrayFramesLoop.clear() #arrayDistancesLoop.clear() #arrayAnglesLoop.clear()
logging.info(str(datetime.datetime.now()) + ": All interactions checked.")
O trecho de código seguinte antecede o processo de criação dos gráficos, e é responsável
por criar um ficheiro que é colocado na pasta de execução com um resumo dos resultados,
ou seja, escreve somente as interações que cumpriram os requisitos, caso existam, e as suas
validação do output da execução. Uma vez que só tem as médias, qualquer informação mais detalhada deve ser consultada no ficheiro individual. De forma geral este ficheiro permite ter uma overview da execução.
if arrayAllResults:
with open(csvresults, "w") as fp:
wr = csv.writer(fp, delimiter='\t',lineterminator='\n') wr.writerow(['Interaction;Distance;Angle'])
for x in arrayAllResults : wr.writerow ([x])
shutil.move(csvresults, newDirectoryName)
De seguida inicia-se o processo de criação dos ficheiros pdf com os gráficos dos ângulos e das distâncias caso existam interações que cumpram os requisitos. Inicialmente o processo cria um ficheiro pdf por cada execução mas, no final, são agrupados num único ficheiro de distâncias e de ângulos contendo todos os pdfs inicialmente gerados.
if(count > 1 ):
logging.info(str(datetime.datetime.now()) + ": Graphics Creation Process Started.") #Plot Distance/Frames
for index,x in enumerate(arrayDistances):
labelString = str(index+1) plt.xlabel("time (ps)") plt.ylabel("Distance (nm)")
#plt.title("Metal_Amine - Distance") plt.title(str(arrayPlotNameDistance[0])) arrayPlotNameDistance.pop(0)
plt.plot(arrayFrames[0],x, label=labelString) #plt.legend()
distancesPlotName = "Distance_Metal_Amine_" + labelString + "_" + str(dateEpoch)+
".pdf"
plt.savefig(distancesPlotName) plt.clf()
pwd=os.getcwd()
pattern = "/Distance_Metal_Amine*"
pdfs = glob.glob(pwd + pattern) merger = PdfMerger()
for pdf in pdfs:
merger.append(pdf)
mergeFilenameDistances = "All_Distances_Metal_Amine_" + str(dateEpoch)+ ".pdf"
merger.write(mergeFilenameDistances) merger.close()
for pdf in pdfs:
os.remove(pdf)
#distancesPlotName = "Distances_SmallUniverse_SinglePlot_Test_" + labelString + "_"
+ str(dateEpoch)+ ".pdf"
#plt.savefig(distancesPlotName)
logging.info(str(datetime.datetime.now()) + ": Plot File '" + str(mergeFilenameDistances) +"' Created on Execution Directory (" + str(newDirectoryName) +").")
shutil.move(mergeFilenameDistances, newDirectoryName)
plt.clf()
#Plot Angles/Frames
for index,x in enumerate(arrayAngles):
labelString = str(index+1) plt.xlabel("time (ps)")
plt.ylabel("Angles Amplitude (º)") #plt.title("Metal_Amine - Angles") plt.title(str(arrayPlotNameAngle[0])) arrayPlotNameAngle.pop(0)
plt.plot(arrayFrames[0],x, label=labelString) #plt.legend()
anglesPlotName ="Angles_Metal_Amine_" + labelString + "_" + str(dateEpoch)+
".pdf"
plt.savefig(anglesPlotName) plt.clf()
pwd=os.getcwd()
pattern = "/Angles_Metal_Amine*"
pdfs = glob.glob(pwd + pattern) merger = PdfMerger()
for pdf in pdfs:
merger.append(pdf)
mergeFilenameAngles = "All_Angles_Metal_Amine_" + str(dateEpoch)+ ".pdf"
merger.write(mergeFilenameAngles) merger.close()
for pdf in pdfs:
os.remove(pdf)
#logging.info(str(datetime.datetime.now()) + ": Plot File Created -> " + str(anglesPlotName))
logging.info(str(datetime.datetime.now()) + ": Plot File '" + str(mergeFilenameAngles) +"' Created on Execution Directory (" + str(newDirectoryName) +").") shutil.move(mergeFilenameAngles, newDirectoryName)
logging.info(str(datetime.datetime.now()) + ": Graphics Creation Process Ended.") logging.info(str(datetime.datetime.now()) + ": Execution Ended.")
shutil.copy(logFilename, newDirectoryName)
shutil.make_archive(newDirectoryName, 'zip', newDirectoryName) #os.remove(logFilename)
else:
logging.info(str(datetime.datetime.now()) + ": No interactions matched the requisites.") logging.info(str(datetime.datetime.now()) + ": Graphics Creation Process will be ignored.")
logging.info(str(datetime.datetime.now()) + ": No plots will be created.") logging.info(str(datetime.datetime.now()) + ": Execution Ended.")
shutil.copy(logFilename, newDirectoryName)
shutil.make_archive(newDirectoryName, 'zip', newDirectoryName)