From e570bd7271713f7dd5e5a0b99087d07f73854385 Mon Sep 17 00:00:00 2001 From: Malte Schokolowski <baw8441@uni-hamburg.de> Date: Fri, 3 Dec 2021 18:28:30 +0100 Subject: [PATCH] fixed bug and removed old Processing.py --- verarbeitung/Processing.py | 262 ++++++++---------- verarbeitung/Processing_pub_objs_only.py | 255 ----------------- verarbeitung/Processing_unittest.py | 2 +- .../__pycache__/Processing.cpython-39.pyc | Bin 4021 -> 4045 bytes .../Processing_pub_objs_only.cpython-39.pyc | Bin 0 -> 4137 bytes 5 files changed, 120 insertions(+), 399 deletions(-) delete mode 100644 verarbeitung/Processing_pub_objs_only.py create mode 100644 verarbeitung/__pycache__/Processing_pub_objs_only.cpython-39.pyc diff --git a/verarbeitung/Processing.py b/verarbeitung/Processing.py index ab7db1d..0dcc739 100644 --- a/verarbeitung/Processing.py +++ b/verarbeitung/Processing.py @@ -23,12 +23,13 @@ from json_demo import output_to_json # adds every publication from input list to graph structure # doi_input_list: list of publication dois from user +def initialize_nodes_list(doi_input_list, search_depth_max, search_height_max, test_var): + references_pub_obj_list = [] + citations_pub_obj_list = [] -# TO-DO: Listenelemente auf Korrektheit überprüfen -def initialize_nodes_list(doi_input_list, test_var): for pub_doi in doi_input_list: - # checks if its a test and chooses input function accordingly + #checks if its a test and chooses input function accordingly if(test_var): pub = input_test_func(pub_doi) else: @@ -46,60 +47,40 @@ def initialize_nodes_list(doi_input_list, test_var): else: doi_input_list.remove(pub_doi) -# adds inner edges between citations and references to edges -def complete_inner_edges(test_var): - for node in nodes: - - # checks if its a test and chooses input function accordingly - if (test_var): - pub = input_test_func(node.doi_url) - else: - pub = input(node.doi_url) + # inserts references as publication objects into list and + # inserts first depth references into nodes/edges if maximum search depth > 0 + for reference in create_graph_structure_references(pub, 0, search_depth_max, test_var): + references_pub_obj_list.append(reference) + # inserts citations as publication objects into list and + # inserts first height citations into nodes if maximum search height > 0 + for citation in create_graph_structure_citations(pub, 0, search_height_max, test_var): + citations_pub_obj_list.append(citation) + return(references_pub_obj_list, citations_pub_obj_list) + + +# adds edges between citation and reference group +def complete_inner_edges(test_var): + for node in nodes: if (node.group == "depth"): - for citation in pub.citations: - if (citation in nodes and [citation.doi_url, pub.doi_url] not in edges): - edges.append([citation.doi_url, pub.doi_url]) - + for citation in node.citations: + for cit in nodes: + if (citation.doi_url == cit.doi_url and [citation.doi_url, node.doi_url] not in edges): + edges.append([citation.doi_url, node.doi_url]) if (node.group == "height"): - for reference in pub.references: - for node in nodes: - if (reference.doi_url in node.doi_url and [pub.doi_url, reference.doi_url] not in edges): - edges.append([pub.doi_url,reference.doi_url]) - - - -# adds a node for every publication unknown -# adds edges for citations between publications -def create_graph_structure_citations(pub, search_height, search_height_max): - for citation in pub.citations: - - # checks if publication already exists in nodes - not_in_nodes = True - for node in nodes: - # checks every citation for duplication - if (citation.doi_url == node.doi_url): - not_in_nodes = False - break - if (not_in_nodes): - if (search_height <= search_height_max): - citation.group = "height" - nodes.append(citation) - edges.append([citation.doi_url,pub.doi_url]) - - # adds only an edge (citation already exists) - elif [citation.doi_url,pub.doi_url] not in edges: - edges.append([citation.doi_url,pub.doi_url]) + for reference in node.references: + for ref in nodes: + if (reference.doi_url == ref.doi_url and [node.doi_url, reference.doi_url] not in edges): + edges.append([node.doi_url,reference.doi_url]) # adds a node for every publication unknown # adds edges for references between publications -def create_graph_structure_references(pub, search_depth, search_depth_max): +def create_graph_structure_references(pub, search_depth, search_depth_max, test_var): + references_pub_obj_list = [] for reference in pub.references: - - # checks if publication already exists in nodes not_in_nodes = True for node in nodes: # checks every reference for duplication @@ -107,86 +88,94 @@ def create_graph_structure_references(pub, search_depth, search_depth_max): not_in_nodes = False break if (not_in_nodes): - if (search_depth <= search_depth_max): - reference.group = "depth" - nodes.append(reference) - edges.append([pub.doi_url,reference.doi_url]) + if (search_depth < search_depth_max): - # adds only an edge (citation already exists) - elif [pub.doi_url,reference.doi_url] not in edges: - edges.append([pub.doi_url,reference.doi_url]) - + #checks if its a test and chooses input function accordingly + if (test_var): + reference_pub_obj = input_test_func(reference.doi_url) + else: + reference_pub_obj = input(reference.doi_url) + reference_pub_obj.group = "depth" + nodes.append(reference_pub_obj) + edges.append([pub.doi_url,reference_pub_obj.doi_url]) + references_pub_obj_list.append(reference_pub_obj) -# recursive function to implement height-first-search on citations -# doi_citations: input list of citet dois -# search_height: current search_height of height-first-search -# search_height_max: maximal search_height for dfs -def process_citations_rec(doi_citations, search_height, search_height_max, test_var): - # height of search is increased by 1 with each recursive call - search_height += 1 + # adds edge only if citation already exists + elif [pub.doi_url,reference.doi_url] not in edges: + edges.append([pub.doi_url,reference.doi_url]) + return references_pub_obj_list - # create class object for every citation from list - for pub_doi in doi_citations: - # checks if its a test and chooses input function accordingly - if (test_var): - pub = input_test_func(pub_doi) - else: - pub = input(pub_doi) +# recursive function to implement height-first-search on references +# references_pub_obj_list: input list of references as publication objects +# search_depth: current search_depth of height-first-search +# search_depth_max: maximal search_depth for dfs +def process_references_rec(references_pub_obj_list, search_depth, search_depth_max, test_var): + # adds next level to nodes/edges + for pub in references_pub_obj_list: + new_reference_pub_obj_list = create_graph_structure_references(pub, search_depth, search_depth_max, test_var) - create_graph_structure_citations(pub, search_height, search_height_max) - # If the maximum height has not yet been reached, all references from the publication - # are written to an array and the function is called again with this array. - if (search_height < search_height_max): - citations_list = [] - for citation in pub.citations: + # If the maximum height has not yet been reached, calls function recursivly with increased height + if (search_depth < search_depth_max): + process_references_rec(new_reference_pub_obj_list, search_depth+1, search_depth_max, test_var) - # currently only the references with acs are stored in the URL, because we can't - # extract the info from other sources. - if ("acs" in citation.doi_url or test_var == True): - citations_list.append(citation.doi_url) - # recursive call of function. - process_citations_rec(citations_list, search_height, search_height_max, test_var) + +# adds a node for every publication unknown +# adds edges for citations between publications +def create_graph_structure_citations(pub, search_height, search_height_max, test_var): + citations_pub_obj_list = [] + for citation in pub.citations: + not_in_nodes = True + for node in nodes: + # checks every citation for duplication + if (citation.doi_url == node.doi_url): + not_in_nodes = False + break + if (not_in_nodes): + if (search_height < search_height_max): + #checks if its a test and chooses input function accordingly + if (test_var): + citation_pub_obj = input_test_func(citation.doi_url) + else: + citation_pub_obj = input(citation.doi_url) -# recursive function to implement height-first-search on references -# doi_references: input list of referenced dois -# search_depth: current search_depth of height-first-search -# search_depth_max: maximal search_depth for dfs -def process_references_rec(doi_references, search_depth, search_depth_max, test_var): - # The depth is increased by 1 with each recursive call - search_depth += 1 + citation_pub_obj.group = "height" + nodes.append(citation_pub_obj) + edges.append([citation_pub_obj.doi_url,pub.doi_url]) + citations_pub_obj_list.append(citation_pub_obj) - # create class object for every citation from list - for pub_doi in doi_references: + # adds only edge if citation already exists + elif [citation.doi_url,pub.doi_url] not in edges: + edges.append([citation.doi_url,pub.doi_url]) + return citations_pub_obj_list - #checks if its a test and chooses input function accordingly - if (test_var): - pub = input_test_func(pub_doi) - else: - pub = input(pub_doi) - create_graph_structure_references(pub, search_depth, search_depth_max) - # If the maximum depth has not yet been reached, all references from the publication - # are written to an array and the function is called again with this array. - if (search_depth < search_depth_max): - references_list = [] - for reference in pub.references: - # currently only the references with acs are stored in the URL, because we can't - # extract the info from other sources. - if ("acs" in reference.doi_url or test_var == True): - references_list.append(reference.doi_url) +# recursive function to implement height-first-search on citations +# citations_pub_obj_list: input list of citations as publication objects +# search_height: current search_height of height-first-search +# search_height_max: maximal search_height for dfs +def process_citations_rec(citations_pub_obj_list, search_height, search_height_max, test_var): + # adds next level to nodes/edges + for pub in citations_pub_obj_list: + new_citation_pub_obj_list = create_graph_structure_citations(pub, search_height, search_height_max, test_var) + + # If the maximum height has not yet been reached, calls function recursivly with increased height + if (search_height < search_height_max): + process_citations_rec(new_citation_pub_obj_list, search_height+1, search_height_max, test_var) - # recursive call of function. - process_references_rec(references_list, search_depth, search_depth_max, test_var) - +# main function to call. Needs as input: +# doi_input_list: input list of dois +# search_height: max search height to process to +# search_depth: max search depth to process to +# test_var: only needed for unit test as True, default is False def process_main(doi_input_list, search_height, search_depth, test_var = False): # ERROR-Handling doi_array = NULL if (len(doi_input_list) == 0): @@ -206,24 +195,30 @@ def process_main(doi_input_list, search_height, search_depth, test_var = False): nodes = [] edges = [] + # initializes nodes/edges from input and gets a list with publication objects for citations and references returned + references_obj_list, citations_obj_list = initialize_nodes_list(doi_input_list,search_depth, search_height, test_var) + + # function calls to begin recursive processing up to max depth/height + process_citations_rec(citations_obj_list, 1, search_height, test_var) + process_references_rec(references_obj_list, 1, search_depth, test_var) - initialize_nodes_list(doi_input_list,test_var) - process_citations_rec(doi_input_list, 0, search_height, test_var) - process_references_rec(doi_input_list, 0, search_depth, test_var) + # adds edges between reference group and citation group of known publications complete_inner_edges(test_var) + # calls a skript to save nodes and edges of graph in .json file output_to_json(nodes,edges) - # only for internal testing - doi_nodes = [] - for node in nodes: - doi_nodes.append(node.doi_url) - return(doi_nodes,edges) + # only for unit tests + if (test_var == True): + doi_nodes_list = [] + for node in nodes: + doi_nodes_list.append(node.doi_url) + return(doi_nodes_list, edges) - +# a function to print nodes and edges from a graph def print_graph(nodes, edges): print("Knoten:\n") for node in nodes: @@ -232,25 +227,13 @@ def print_graph(nodes, edges): for edge in edges: print(edge,"\n") - -# function to test cycles -def test_cycle(): - arr = [] - arr.append('doiz1') - #arr.append('doiz2') - - nodes,edges = process_main(arr,1,1,True) - - print(nodes, edges) - - print_graph(nodes, edges) -# program test, because there is no connection to the input yet. -def test_print(): - arr = [] - #arr.append('https://pubs.acs.org/doi/10.1021/acs.jcim.9b00249') +# program test, because there is no connection to UI yet. +def try_known_publications(): + doi_list = [] + doi_list.append('https://pubs.acs.org/doi/10.1021/acs.jcim.9b00249') #arr.append('https://pubs.acs.org/doi/10.1021/acs.jcim.9b00249') - #arr.append('https://doi.org/10.1021/acs.jmedchem.0c01332') + doi_list.append('https://doi.org/10.1021/acs.jmedchem.0c01332') #arr.append('https://doi.org/10.1021/acs.jcim.0c00741') #arr.append('https://doi.org/10.1021/ci700007b') @@ -259,13 +242,6 @@ def test_print(): #arr.append[url] - nodes,edges = process_main(arr,2,2,True) - - print_graph(nodes, edges) - -#test_print() -#test_cycle() -#print(process_main(['doiz1'],1,1,True)) -#print(process_main(['doi1'],0,0,True)) + nodes,edges = process_main(doi_list,2,2) - \ No newline at end of file + print_graph(nodes, edges) \ No newline at end of file diff --git a/verarbeitung/Processing_pub_objs_only.py b/verarbeitung/Processing_pub_objs_only.py deleted file mode 100644 index a6c1ed3..0000000 --- a/verarbeitung/Processing_pub_objs_only.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Functions to generate a graph representing citations between multiple ACS/Nature journals - -""" - -__authors__ = "Donna Löding, Alina Molkentin, Xinyi Tang, Judith Große, Malte Schokolowski" -__email__ = "cis-project2021@zbh.uni-hamburg.de" -__status__ = "Production" -#__copyright__ = "" -#__credits__ = ["", "", "", ""] -#__license__ = "" -#__version__ = "" -#__maintainer__ = "" - -from bs4 import BeautifulSoup as bs -import requests as req -import sys -from pathlib import Path -from input_fj import input -from input_test import input_test_func -from json_demo import output_to_json - -# adds every publication from input list to graph structure -# doi_input_list: list of publication dois from user -def initialize_nodes_list(doi_input_list, search_depth_max, search_height_max, test_var): - references_pub_obj_list = [] - citations_pub_obj_list = [] - - for pub_doi in doi_input_list: - - #checks if its a test and chooses input function accordingly - if(test_var): - pub = input_test_func(pub_doi) - else: - pub = input(pub_doi) - - # checks if publication already exists in nodes - not_in_nodes = True - for node in nodes: # checks if a pub is already in nodes - if (pub.doi_url == node.doi_url): - not_in_nodes = False - break - if (not_in_nodes): - nodes.append(pub) - pub.group = "input" - else: - doi_input_list.remove(pub_doi) - - # inserts references as publication objects into list and - # inserts first depth references into nodes/edges if maximum search depth > 0 - for reference in create_graph_structure_references(pub, 0, search_depth_max, test_var): - references_pub_obj_list.append(reference) - - # inserts citations as publication objects into list and - # inserts first height citations into nodes if maximum search height > 0 - for citation in create_graph_structure_citations(pub, 0, search_height_max, test_var): - citations_pub_obj_list.append(citation) - - return(references_pub_obj_list, citations_pub_obj_list) - - -# adds edges between citation and reference group -def complete_inner_edges(test_var): - for node in nodes: - if (node.group == "depth"): - for citation in node.citations: - if (citation in nodes and [citation.doi_url, node.doi_url] not in edges): - edges.append([citation.doi_url, node.doi_url]) - if (node.group == "height"): - for reference in node.references: - if (reference in nodes and [node.doi_url, reference.doi_url] not in edges): - edges.append([node.doi_url,reference.doi_url]) - - - - -# adds a node for every publication unknown -# adds edges for references between publications -def create_graph_structure_references(pub, search_depth, search_depth_max, test_var): - references_pub_obj_list = [] - for reference in pub.references: - not_in_nodes = True - for node in nodes: - # checks every reference for duplication - if (reference.doi_url == node.doi_url): - not_in_nodes = False - break - if (not_in_nodes): - if (search_depth < search_depth_max): - - #checks if its a test and chooses input function accordingly - if (test_var): - reference_pub_obj = input_test_func(reference.doi_url) - else: - reference_pub_obj = input(reference.doi_url) - - reference_pub_obj.group = "depth" - nodes.append(reference_pub_obj) - edges.append([pub.doi_url,reference_pub_obj.doi_url]) - references_pub_obj_list.append(reference_pub_obj) - - # adds edge only if citation already exists - elif [pub.doi_url,reference.doi_url] not in edges: - edges.append([pub.doi_url,reference.doi_url]) - return references_pub_obj_list - - -# recursive function to implement height-first-search on references -# references_pub_obj_list: input list of references as publication objects -# search_depth: current search_depth of height-first-search -# search_depth_max: maximal search_depth for dfs -def process_references_rec(references_pub_obj_list, search_depth, search_depth_max, test_var): - # adds next level to nodes/edges - for pub in references_pub_obj_list: - new_reference_pub_obj_list = create_graph_structure_references(pub, search_depth, search_depth_max, test_var) - - # If the maximum height has not yet been reached, calls function recursivly with increased height - if (search_depth < search_depth_max): - process_references_rec(new_reference_pub_obj_list, search_depth+1, search_depth_max, test_var) - - - - -# adds a node for every publication unknown -# adds edges for citations between publications -def create_graph_structure_citations(pub, search_height, search_height_max, test_var): - citations_pub_obj_list = [] - for citation in pub.citations: - not_in_nodes = True - for node in nodes: - # checks every citation for duplication - if (citation.doi_url == node.doi_url): - not_in_nodes = False - break - if (not_in_nodes): - if (search_height < search_height_max): - - #checks if its a test and chooses input function accordingly - if (test_var): - citation_pub_obj = input_test_func(citation.doi_url) - else: - citation_pub_obj = input(citation.doi_url) - - citation_pub_obj.group = "height" - nodes.append(citation_pub_obj) - edges.append([citation_pub_obj.doi_url,pub.doi_url]) - citations_pub_obj_list.append(citation_pub_obj) - - # adds only edge if citation already exists - elif [citation.doi_url,pub.doi_url] not in edges: - edges.append([citation.doi_url,pub.doi_url]) - return citations_pub_obj_list - - - -# recursive function to implement height-first-search on citations -# citations_pub_obj_list: input list of citations as publication objects -# search_height: current search_height of height-first-search -# search_height_max: maximal search_height for dfs -def process_citations_rec(citations_pub_obj_list, search_height, search_height_max, test_var): - # adds next level to nodes/edges - for pub in citations_pub_obj_list: - new_citation_pub_obj_list = create_graph_structure_citations(pub, search_height, search_height_max, test_var) - - # If the maximum height has not yet been reached, calls function recursivly with increased height - if (search_height < search_height_max): - process_citations_rec(new_citation_pub_obj_list, search_height+1, search_height_max, test_var) - - - - - -def process_main(doi_input_list, search_height, search_depth, test_var = False): - # ERROR-Handling doi_array = NULL - if (len(doi_input_list) == 0): - print("Error, no input data") - - # ERROR- if a negative number is entered for height - if (search_height < 0): - print("Error, search_height of search must be positive") - - # ERROR- if a negative number is entered for depth - if (search_depth < 0): - print("Error, search_depth of search must be positive") - - # create empty array for the nodes - # create empty array for the edges - global nodes, edges - nodes = [] - edges = [] - - # initializes nodes/edges from input and gets a list with publication objects for citations and references returned - references_obj_list, citations_obj_list = initialize_nodes_list(doi_input_list,search_depth, search_height, test_var) - process_citations_rec(citations_obj_list, 1, search_height, test_var) - process_references_rec(references_obj_list, 1, search_depth, test_var) - complete_inner_edges(test_var) - - output_to_json(nodes,edges) - - # only for internal testing - doi_nodes = [] - for node in nodes: - doi_nodes.append(node.doi_url) - return(doi_nodes,edges) - - - - -# a function to print nodes and edges from a graph -def print_graph(nodes, edges): - print("Knoten:\n") - for node in nodes: - print(node.title, "\n") - print("\nKanten:\n") - for edge in edges: - print(edge,"\n") - - -# function to test cycles -def test_cycle(): - arr = [] - arr.append('doiz1') - #arr.append('doiz2') - - nodes,edges = process_main(arr,1,1,True) - - print(nodes, edges) - - print_graph(nodes, edges) - -# program test, because there is no connection to the input yet. -def test_print(): - arr = [] - #arr.append('https://pubs.acs.org/doi/10.1021/acs.jcim.9b00249') - #arr.append('https://pubs.acs.org/doi/10.1021/acs.jcim.9b00249') - #arr.append('https://doi.org/10.1021/acs.jmedchem.0c01332') - #arr.append('https://doi.org/10.1021/acs.jcim.0c00741') - - #arr.append('https://doi.org/10.1021/ci700007b') - #arr.append('https://doi.org/10.1021/acs.jcim.5b00292') - #url = sys.argv[1] - #arr.append[url] - - - nodes,edges = process_main(arr,2,2,True) - - print_graph(nodes, edges) - -#test_print() -#test_cycle() -#print(process_main(['doiz1'],1,1,True)) -#print(process_main(['doi1'],0,0,True)) - - \ No newline at end of file diff --git a/verarbeitung/Processing_unittest.py b/verarbeitung/Processing_unittest.py index 772d572..fd11131 100644 --- a/verarbeitung/Processing_unittest.py +++ b/verarbeitung/Processing_unittest.py @@ -1,5 +1,5 @@ import unittest -from Processing import process_main +from Processing_pub_objs_only import process_main class ProcessingTest(unittest.TestCase): def testCycle(self): diff --git a/verarbeitung/__pycache__/Processing.cpython-39.pyc b/verarbeitung/__pycache__/Processing.cpython-39.pyc index e08ca682bcdfcff17580d1e2c0923b6aac9ce00d..54c63251bbf3affbdd176d3d55f4956c2fc08406 100644 GIT binary patch delta 400 zcmdlge^#D1k(ZZ?fq{YHzuT(B-i^E~nZ?-{7#P?Y7#N%x7#NC=FfcHbFt{+p?wWj- zS+%~PhAE5Hg(23amKiK+RLfGsoW)eblEswGRAdEaGi5QQGnKHFF&0^suq|Lmh%IDf zWGJ*MG^%0DVo3q1XQ^Sz;sBe*3O9`vZdykTQx;PVE8Mh>5;l<q><B@yaUF#XlgnAc z8QCVEXOZNHV#~`<NsTw0{DQ?)Tn6MVMh<2cMjj?EW-dl9Mh-?9MzPICtj>&#KAX$f zq8S-eCYP}(Z~n?|#>iMVxsFS5voEIsBjbY2bzIjO8TU^P<9W{bWU?J^5o6Tkjl6!M z??6EU4lYI(MlNO{CN8ETy~!ec(Tuv2AM#3y6%=LWmHYxJ0tF%iBMXlZQ<1^s`F#7h l^%xi!iVQ%6&}2t8;mOtf+Wb-+5*$(-N*wZBsvKM#JOHlLTay3) delta 346 zcmX>rzg3<$k(ZZ?fq{YHvBk<nuZ_GbnZ;Qd7#P?Y7#N%x7#NDTF)%QcFt{+pu9<w6 zSyefyhAE5Hg&|h2mbr$xgrSU~$f$-ni>Zbwiz%I{gsqIR$Y`=6i?F{@4PzEd3R5;y z5lanI7KaN%tW7No*c_u;#u}C^rW(d9rZR@2Dv(YPAF8{mglz#kLTn);BST?TVbSDP zmT*Sa$#+>KC;wwH6_o-xn302-g^`Dei<yg&i&276WV08mGb5w-=03J)M#kjHeQe5` zc{$7&8EYp`<5Jw5%4xvJIDhjruIr49`zGh{JZF47IgGc6@z&&%yndo@L0$z110xF~ z7qbu(7gLe`WF5X}M!m@$d~$+6LGo;1TUmI7n2HQ1Z|B>`rq95@P-Hl{n%|jUl0$++ PibIJ*o=cU3i-QLMj-5@$ diff --git a/verarbeitung/__pycache__/Processing_pub_objs_only.cpython-39.pyc b/verarbeitung/__pycache__/Processing_pub_objs_only.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ce1023e6ea54e1b04b37ad5a1fd08115d5f52a4 GIT binary patch literal 4137 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 C633hX literal 0 HcmV?d00001 -- GitLab