#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "profile.h" #include "mrsReader.h" #include "unicode.h" using namespace std; void parse_options(int argc, char **argv, string *gprof, string *tprof, bool *ignore, bool *stats, bool *quiet, int *itemno, int *parseno, bool *verbose, bool *veryverbose, bool *score_p); void addCounts(char index, map > &totals, map &counts); //prints raw stats in tab-delimited form for use in statistical significance //testing void printStats(tIid item, map &ccounts, map &gcounts, map &tcounts); void printScores(map > &totals); //tests specified or (default) top reading against (all) gold and adds counts to //total pair,string> finishItem(Profile &test, tIid item, int parse_id, map > &totals, vector &goldmrs, bool stats, pair,string> lastline, bool verbose, bool veryverbose, bool ignore); //tests all readings against (all) gold and outputs individual fscores in an //@-delimited file format pair,string> finishFullItem(Profile &test, tIid item, vector &goldmrs, pair,string> lastline); string subspan(string itemstr, string span); int main (int argc, char **argv) { string gprofile, tprofile; bool ignore, stats, quiet, verbose, veryverbose, score_p; int itemno, parseno; parse_options(argc, argv, &gprofile, &tprofile, &ignore, &stats, &quiet, &itemno, &parseno, &verbose, &veryverbose, &score_p); initialize_encoding_converter("utf-8"); Profile gold(gprofile); Profile test(tprofile); vector goldmrs; pair, string> gres = gold.getResult(MRS); pair, string> lastline = pair, string>(pair(-1,-1),string()); tIid item = -1; map > totals; while (gres.first.first >= 0 //still returning results && (itemno == -1 || gres.first.first <= itemno)) { if (itemno > -1 && gres.first.first < itemno) { //looking for a specific item gres = gold.getResult(MRS); continue; // and not there yet } if (gres.first.first != item) { //new item if (item != -1 && goldmrs.size() > 0) { //finish last item if (score_p) lastline = finishFullItem(test, item, goldmrs, lastline); else lastline = finishItem(test, item, parseno, totals, goldmrs, stats, lastline, verbose, veryverbose, ignore); } } item = gres.first.first; if (!ignore || test.getReadings(item) > 0) { //if we are ignoring items that have a gold reading but didn't parse in //test, skip this if there are no test readings if (gold.isGold(item, gres.first.second) && !gres.second.empty()) { //make sure it is annotated as gold (maybe an option to skip this //check?) tMRS *gmrs = new tMRS(gres.second); goldmrs.push_back(gmrs); } } gres = gold.getResult(MRS); } //finish last item if ((itemno == -1 || item == itemno) && goldmrs.size() > 0) { if (score_p) lastline = finishFullItem(test, item, goldmrs, lastline); else lastline = finishItem(test, item, parseno, totals, goldmrs, stats, lastline, verbose, veryverbose, ignore); if (!quiet) printScores(totals); } else cerr << "Item " << itemno << " was not found in the gold profile." << endl; } void parse_options(int argc, char **argv, string *gprof, string *tprof, bool *ignore, bool *stats, bool *quiet, int *itemno, int *parseno, bool *verbose, bool *veryverbose, bool *score_p) { namespace po = boost::program_options; po::options_description visible("Options"); visible.add_options() ("help,h", "This usage information.") ("ignore,i", "Ignore gold triples where the test parse failed.") ("stats,s", "Print raw counts per item (for significance testing).") ("quiet,q", "Don't print totals. (Only makes sense with stats option).") ("verbose,v", "Print unmatched triples.") ("debug,d", "Print all triples and counts.") ("num,n", po::value(itemno)->default_value(-1), "Select a single item to evaluate (-1 for all items).") ("parse,p", po::value(parseno)->default_value(0), "Reading to evaluate, defaults to 0 for the top reading") ("full,f", "Score every reading and output an fscore file") ; po::options_description hidden("Hidden options"); hidden.add_options() ("goldprof", po::value(gprof), "gold profile") ("testprof", po::value(tprof), "test profile") ; po::options_description cmd_line ("Command line options"); cmd_line.add(visible).add(hidden); po::positional_options_description p; p.add("goldprof", 1).add("testprof", 1); po::variables_map vm; try { po::store(po::command_line_parser(argc, argv). options(cmd_line).positional(p).run(), vm); notify(vm); if (vm.count("help")) { cout << "Usage: " << argv[0] << " [options] " << "gold-profile test-profile" << endl; cout << visible << endl; exit(0); } if (!vm.count("goldprof") || !vm.count("testprof")) { cerr << "Insufficient arguments given." << endl; cerr << "Usage: " << argv[0] << " [options] " << "gold-profile test-profile" << endl; cerr << visible << endl; exit(1); } if (vm.count("ignore")) *ignore = true; else *ignore = false; if (vm.count("verbose")) *verbose = true; else *verbose = false; if (vm.count("debug")) *veryverbose = true; else *veryverbose = false; if (vm.count("stats")) *stats = true; else *stats = false; if (vm.count("quiet")) { if (!vm.count("stats")) cerr << "WARNING: using -q without -s means no output will be produced." << " Continuing anyway." << endl; *quiet = true; } else *quiet = false; if (vm.count("full")) { *score_p = true; *quiet = true; *stats = false; } else *score_p = false; } catch (po::error& e ) { cerr << "Error: " << e.what() << endl; cerr << "Usage: " << argv[0] << " [options] " << "gold-profile test-profile" << endl; cerr << visible << endl; exit(1); } } //add counts for a single comparison to the total, index specifies whether the //counts are for gold (G), test (T) or correct (C) void addCounts(char index, map > &totals, map &counts) { for (map::iterator it = counts.begin(); it != counts.end(); ++it) { if (totals.count(it->first) == 0) totals[it->first] = map(); if (totals[it->first].count(index) == 0) totals[it->first][index] = 0; totals[it->first][index]+= it->second; } } void printStats(tIid item, map &ccounts, map &gcounts, map &tcounts) { cout << item << ".gz\t" << ccounts["ALL"] << "\t" << gcounts["ALL"] << "\t" << tcounts["ALL"] << "\t" << ccounts["ARGS"] << "\t" << gcounts["ARGS"] << "\t" << tcounts["ARGS"] << "\t" << ccounts["NAMES"] << "\t" << gcounts["NAMES"] << "\t" << tcounts["NAMES"] << "\t" << ccounts["NAMES"]+ccounts["ARGS"] << "\t" << gcounts["NAMES"]+gcounts["ARGS"] << "\t" << tcounts["NAMES"]+tcounts["ARGS"] << "\t" << endl; } void printScores(map > &totals) { cout << setw(10) << " " << setw(7) << " PREC " << setw(7) << " REC " << setw(7) << "F" << setw(7) << " TOTAL" << endl; cout << setw(10) << "EDM" << " " << fixed << setprecision(3) << setw(7) << (totals["ALL"]['T']>0? static_cast(totals["ALL"]['C'])/totals["ALL"]['T']:0) << " " << setw(7) << (totals["ALL"]['G']>0? static_cast(totals["ALL"]['C'])/totals["ALL"]['G']:0) << " " << setw(7) << (totals["ALL"]['G']>0? (2.0*totals["ALL"]['C'])/(totals["ALL"]['T']+totals["ALL"]['G']):0) << " " << setw(7) << totals["ALL"]['G'] << endl; cout << setw(10) << "EDM_NA" << " " << fixed << setprecision(3) << setw(7) << (totals["NAMES"]['T']>0? (static_cast(totals["ARGS"]['C']) + totals["NAMES"]['C']) /(totals["ARGS"]['T']+totals["NAMES"]['T']):0) << " " << setw(7) << (totals["NAMES"]['G']>0? (static_cast(totals["ARGS"]['C']) + totals["NAMES"]['C']) /(totals["ARGS"]['G']+totals["NAMES"]['G']):0) << " " << setw(7) << (totals["NAMES"]['G']>0? (2.0*(totals["ARGS"]['C']+totals["NAMES"]['C'])) /(totals["ARGS"]['T']+totals["NAMES"]['T']+totals["ARGS"]['G'] +totals["NAMES"]['G']):0) << " " << setw(7) << totals["ARGS"]['G']+totals["NAMES"]['G'] << endl; cout << setw(10) << "EDM_A" << " " << fixed << setprecision(3) << setw(7) << (totals["ARGS"]['T']>0? static_cast(totals["ARGS"]['C'])/totals["ARGS"]['T']:0) << " " << setw(7) << (totals["ARGS"]['G']>0? static_cast(totals["ARGS"]['C'])/totals["ARGS"]['G']:0) << " " << setw(7) << (totals["ARGS"]['G']>0? (2.0*totals["ARGS"]['C'])/(totals["ARGS"]['T']+totals["ARGS"]['G']):0) << " " << setw(7) << totals["ARGS"]['G'] << endl; cout << setw(10) << "EDM_N" << " " << fixed << setprecision(3) << setw(7) << (totals["NAMES"]['T']>0? static_cast(totals["NAMES"]['C'])/totals["NAMES"]['T']:0) << " " << setw(7) << (totals["NAMES"]['G']>0? static_cast(totals["NAMES"]['C'])/totals["NAMES"]['G']:0) << " " << setw(7) << (totals["NAMES"]['G']>0? (2.0*totals["NAMES"]['C'])/(totals["NAMES"]['T']+totals["NAMES"]['G']):0) << " " << setw(7) << totals["NAMES"]['G'] << endl; cout << setw(10) << "EDM_P" << " " << fixed << setprecision(3) << setw(7) << (totals["PROPS"]['T']>0? static_cast(totals["PROPS"]['C'])/totals["PROPS"]['T']:0) << " " << setw(7) << (totals["PROPS"]['G']>0? static_cast(totals["PROPS"]['C'])/totals["PROPS"]['G']:0) << " " << setw(7) << (totals["PROPS"]['G']>0? (2.0*totals["PROPS"]['C'])/(totals["PROPS"]['T']+totals["PROPS"]['G']):0) << " " << setw(7) << totals["PROPS"]['G'] << endl << endl; for (map >::iterator it = totals.begin(); it != totals.end(); ++it) { if (it->first != "ALL" && it->first != "ARGS" && it->first != "NAMES" && it->first != "PROPS") { cout << setw(10) << it->first << " " << fixed << setprecision(3) << setw(7) << (totals[it->first]['T']>0? static_cast(totals[it->first]['C'])/ totals[it->first]['T']:0) << " " << setw(7) << (totals[it->first]['G']>0? static_cast(totals[it->first]['C'])/ totals[it->first]['G']:0) << " " << setw(7) << (totals[it->first]['G']>0? (2.0*totals[it->first]['C'])/ (totals[it->first]['T']+totals[it->first]['G']):0) << " " << setw(7) << totals[it->first]['G'] << endl; } } } pair,string> finishItem(Profile &test, tIid item, int parse_id, map > &totals, vector &goldmrs, bool stats, pair,string> lastline, bool verbose, bool veryverbose, bool ignore) { pair,string> tres = lastline; if (test.getReadings(item) <= parse_id) { //just add counts if (parse_id > 0) cerr << "No reading " << parse_id << " available for item " << item << "." << endl; if (!ignore) { addCounts('G', totals, goldmrs[0]->getCounts()); if (veryverbose) { cout << "GOLD TRIPLES: " << item << endl; goldmrs[0]->printTriples(); goldmrs[0]->printCounts(); } if (stats) { map zeros; printStats(item, zeros, goldmrs[0]->getCounts(), zeros); } } } else { if (tres.first.first == -1) tres = test.getResult(MRS); while (tres.first.first != -1 && tres.first.first < item) tres = test.getResult(MRS); if (tres.first.first != item) { cerr << "Item " << item << " is not in the test profile." << endl; if (!ignore) { addCounts('G', totals, goldmrs[0]->getCounts()); if (stats) { map zeros; printStats(item, zeros, goldmrs[0]->getCounts(), zeros); } } } else { while (tres.first.first != -1 && tres.first.second < parse_id) tres = test.getResult(MRS); if (tres.first.first != item || tres.first.second != parse_id || tres.second.empty()) { cerr << "No reading " << parse_id << " for item " << item << " in the test profile." << endl; if (!ignore) { addCounts('G', totals, goldmrs[0]->getCounts()); if (stats) { map zeros; printStats(item, zeros, goldmrs[0]->getCounts(), zeros); } } } else {//we have an appropriate test reading to compare tMRS *tmrs = new tMRS(tres.second); pair, vector > maxscores; double maxscore = 0; tMRS *bestgold = NULL; //compare this reading to each gold MRS for (vector::iterator it = goldmrs.begin(); it != goldmrs.end(); ++it) { pair, vector > correct = (*it)->compareTriples(*tmrs); double fscore = (*it)->getCounts()["ALL"]>0? ((2.0*correct.first["ALL"])/ (tmrs->getCounts()["ALL"]+(*it)->getCounts()["ALL"])):0; if (bestgold == NULL || fscore > maxscore) { //and use the match with the highest EDM fscore as the final //match maxscores = correct; maxscore = fscore; bestgold = *it; } } addCounts('G', totals, bestgold->getCounts()); addCounts('T', totals, tmrs->getCounts()); addCounts('C', totals, maxscores.first); if (veryverbose) { //print all triples (debugging) cout << "GOLD TRIPLES: " << item << endl; bestgold->printTriples(); bestgold->printCounts(); cout << "TEST TRIPLES: " << item << endl; tmrs->printTriples(); tmrs->printCounts(); } if(verbose && !maxscores.second.empty()) { // print unmatched triples cout << "Unmatched triples for " << item << ":" << test.getItem(item) << endl; sort(maxscores.second.begin(), maxscores.second.end(), unmatched_cmp()); for (vector::iterator it = maxscores.second.begin(); it != maxscores.second.end(); ++it) { ostringstream relstr1, relstr2; string ss(subspan(test.getItem(item), it->span1)); if (ss.length() > 23 ) ss = string(ss.substr(0,20)+"..."); relstr1 << ss << " " << it->span1; if ((it->span2).empty()) relstr2 << it->name2.substr(0,23); else { ss = string(subspan(test.getItem(item), it->span2)); if (ss.length() > 23 ) ss = string(ss.substr(0,20)+"..."); relstr2 << ss << " " << it->span2; } cout << it->dir << " " << left << setw(33) << relstr1.str() << " " << left << setw(7) << it->pred << " " << relstr2.str() << endl; } } if (stats) printStats(item, maxscores.first, bestgold->getCounts(), tmrs->getCounts()); } } } for (vector::iterator it = goldmrs.begin(); it != goldmrs.end(); ++it) delete *it; goldmrs.clear(); return tres; } pair,string> finishFullItem(Profile &test, tIid item, vector &goldmrs, pair,string> lastline) { //record lastline to save re-reading the entire file every time pair,string> tres = lastline; if (tres.first.first == -1) tres = test.getResult(MRS); while (tres.first.first != -1 && tres.first.first < item) tres = test.getResult(MRS); while (tres.first.first == item && !tres.second.empty()) { tMRS *tmrs = new tMRS(tres.second); pair, vector > maxscores; double maxscore = 0; tMRS *bestgold = NULL; // compare analysis to each gold for (vector::iterator it = goldmrs.begin(); it != goldmrs.end(); ++it) { pair, vector > correct = (*it)->compareTriples(*tmrs); double fscore = (*it)->getCounts()["ALL"]>0? ((2.0*correct.first["ALL"])/ (tmrs->getCounts()["ALL"]+(*it)->getCounts()["ALL"])):0; if (bestgold == NULL || fscore > maxscore) { //and use the match with the highest EDM fscore as the final match maxscores = correct; maxscore = fscore; bestgold = *it; } } //print item no, parse no and fscores cout << item << "@" << tres.first.second; cout << "@" << fixed << setprecision(3) << (bestgold->getCounts()["ALL"]>0? (2.0*maxscores.first["ALL"])/ (tmrs->getCounts()["ALL"]+bestgold->getCounts()["ALL"]):0); cout << "@" << fixed << setprecision(3) << (bestgold->getCounts()["NAMES"]>0? (2.0*(maxscores.first["ARGS"]+maxscores.first["NAMES"])) /(tmrs->getCounts()["ARGS"]+tmrs->getCounts()["NAMES"] +bestgold->getCounts()["ARGS"]+bestgold->getCounts()["NAMES"]):0); cout << "@" << fixed << setprecision(3) << (bestgold->getCounts()["ARGS"]>0? (2.0*maxscores.first["ARGS"]) /(tmrs->getCounts()["ARGS"] +bestgold->getCounts()["ARGS"]):0); cout << "@" << fixed << setprecision(3) << (bestgold->getCounts()["NAMES"]>0? (2.0*maxscores.first["NAMES"]) /(tmrs->getCounts()["NAMES"] +bestgold->getCounts()["NAMES"]):0); cout << "@" << fixed << setprecision(3) << (bestgold->getCounts()["PROPS"]>0? (2.0*maxscores.first["PROPS"]) /(tmrs->getCounts()["PROPS"] +bestgold->getCounts()["PROPS"]):0); cout << endl; tres = test.getResult(MRS); } for (vector::iterator it = goldmrs.begin(); it != goldmrs.end(); ++it) delete *it; goldmrs.clear(); return tres; } string subspan(string itemstr, string span) { string substr; if (span.at(0) != '<') return substr; int mid = span.find(':'); if (mid == string::npos) return substr; istringstream startstr(span.substr(1, mid-1)); istringstream endstr(span.substr(mid+1, span.find('>', mid)-(mid+1))); int start, end; startstr >> start; endstr >> end; if (start >= 0 && end > 0) { UnicodeString uitem = Conv->convert(itemstr); if (start < uitem.length() && end <= uitem.length()) { UnicodeString subuitem(uitem, start, end-start); substr = Conv->convert(subuitem); } else return substr; } else return substr; return substr; }