123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931 |
- /* OTGrammar.cpp
- *
- * Copyright (C) 1997-2018 Paul Boersma
- *
- * This code is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or (at
- * your option) any later version.
- *
- * This code is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this work. If not, see <http://www.gnu.org/licenses/>.
- */
- /*
- * pb 2002/07/16 GPL
- * pb 2002/11/04 randomize in case of equal candidates
- * pb 2003/05/08 better superset violation warning
- * pb 2003/05/23 made superset violation warning conditional
- * pb 2003/10/15 backtrack in case of failing multiple chews for EDCD
- * pb 2003/10/15 crucial ties option
- * pb 2004/01/17 OTGrammar_Distributions_getFractionCorrect
- * pb 2004/08/08 OTGrammar_removeHarmonicallyBoundedCandidates
- * pb 2004/08/09 bug removal: more complete OTGrammar_save and restore (affected multiple-chew correctness),
- * changing the 114.5 in Boersma (Phonology 2003) to 118.1
- * pb 2004/08/09 suppressed superset violation in case of identical constraint violation patterns such
- * as for /(L L2) L (L2 L) (L1 L)/ and /(L2 L) L (L L2) (L1 L)/, thus restricting the warning to cases
- * of *strict* superset violations
- * pb 2004/08/11 repaired memory leak in OTGrammarTableau_removeCandidate_unstripped
- * pb 2004/09/10 monitor rankings during learning from PairDistribution or Distributions
- * pb 2004/10/16 struct structOTxx
- * pb 2005/01/24 write to headerless spreadsheet file
- * pb 2005/04/19 OTHistory
- * pb 2005/06/30 learning from partial pairs
- * pb 2005/12/11 OTGrammar_honourlocalRankings:
- * pb 2005/12/11 OTGrammar_PairDistribution_listObligatoryRankings (depth 1)
- * pb 2006/01/05 new decision strategies: HarmonicGrammar and LinearOT
- * pb 2006/01/21 better procedure name
- * pb 2006/02/02 new decision strategy: ExponentialHG
- * pb 2006/12/08 MelderInfo
- * pb 2007/04/22 multiply learning step by number of violations (for HarmonicGrammar and LinearOT)
- * pb 2007/04/25 new decision strategy: MaximumEntropy
- * pb 2007/04/30 many improvements
- * pb 2007/05/20 new decision strategy: PositiveHG
- * pb 2007/06/21 corrected PositiveHG
- * pb 2007/06/21 made spreadsheet file readable as Table
- * pb 2007/07/24 leak and constraint plasticity
- * pb 2007/07/27 leak and constraint plasticity also written...
- * pb 2007/08/08 wchar
- * pb 2007/10/01 can write as encoding
- * pb 2008/03/03 EDCD with vacation
- * pb 2008/03/07 Demote one with vacation
- * pb 2008/03/07 Reset to random total ranking
- * pb 2008/03/27 Exponential HG: reset average weight to zero after every change
- * pb 2008/03/28 Exponential HG: set update rule to HG-GLA rather than OT-GLA
- * pb 2008/03/31 OTGrammar_PairDistribution_findPositiveWeights
- * pb 2008/04/08 made (OTGrammar & Distributions) learnFromPartialOutputs and getFractionCorrect five times faster
- * pb 2008/04/12 split off NUMlinprog
- * pb 2008/05/31 new decision strategy: ExponentialMaximumEntropy
- * pb 2009/03/09 new update rule: Weighted all up, highest down
- * pb 2009/07/07 OTGrammar_PairDistribution_getMinimumNumberCorrect
- * pb 2010/06/05 corrected colours
- * pb 2011/03/01 renamed "strategy" to "updateRule", "meanLearningStep" to "plasticity", "rankingSpreading" to "evaluationNoise"
- * pb 2011/03/22 C++
- * pb 2011/04/27 Melder_debug 41 and 42
- * pb 2011/07/14 C++
- * pb 2014/02/27 skippable symmetric all
- * pb 2014/07/25 RRIP
- */
- #include "OTGrammar.h"
- #include "oo_DESTROY.h"
- #include "OTGrammar_def.h"
- #include "oo_COPY.h"
- #include "OTGrammar_def.h"
- #include "oo_EQUAL.h"
- #include "OTGrammar_def.h"
- #include "oo_CAN_WRITE_AS_ENCODING.h"
- #include "OTGrammar_def.h"
- #include "oo_WRITE_BINARY.h"
- #include "OTGrammar_def.h"
- #include "oo_READ_BINARY.h"
- #include "OTGrammar_def.h"
- #include "oo_DESCRIPTION.h"
- #include "OTGrammar_def.h"
- #include "enums_getText.h"
- #include "OTGrammar_enums.h"
- #include "enums_getValue.h"
- #include "OTGrammar_enums.h"
- void structOTGrammar :: v_info ()
- {
- structDaata :: v_info ();
- integer numberOfCandidates = 0, numberOfViolations = 0;
- for (integer itab = 1; itab <= numberOfTableaus; itab ++) {
- numberOfCandidates += tableaus [itab]. numberOfCandidates;
- for (integer icand = 1; icand <= tableaus [itab]. numberOfCandidates; icand ++)
- for (integer icons = 1; icons <= numberOfConstraints; icons ++)
- numberOfViolations += tableaus [itab]. candidates [icand]. marks [icons];
- }
- MelderInfo_writeLine (U"Decision strategy: ", kOTGrammar_decisionStrategy_getText (decisionStrategy));
- MelderInfo_writeLine (U"Number of constraints: ", numberOfConstraints);
- MelderInfo_writeLine (U"Number of tableaus: ", numberOfTableaus);
- MelderInfo_writeLine (U"Number of candidates: ", numberOfCandidates);
- MelderInfo_writeLine (U"Number of violation marks: ", numberOfViolations);
- }
- void structOTGrammar :: v_writeText (MelderFile file) {
- MelderFile_write (file, U"\n<", kOTGrammar_decisionStrategy_getText (decisionStrategy),
- U">\n", leak, U" ! leak\n", numberOfConstraints, U" constraints");
- for (integer icons = 1; icons <= numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & constraints [icons];
- MelderFile_write (file, U"\nconstraint [", icons, U"]: \"");
- for (const char32 *p = & constraint -> name [0]; *p; p ++) {
- if (*p == U'\"')
- MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
- MelderFile_writeCharacter (file, *p);
- }
- MelderFile_write (file, U"\" ", constraint -> ranking,
- U" ", constraint -> disharmony, U" ", constraint -> plasticity, U" ! ");
- for (const char32 *p = & constraint -> name [0]; *p; p ++) {
- if (*p == U'\n')
- MelderFile_writeCharacter (file, U' ');
- else if (*p == U'\\' && p [1] == U's' && p [2] == U'{')
- p += 2;
- else if (*p == U'}')
- { }
- else
- MelderFile_writeCharacter (file, *p);
- }
- }
- MelderFile_write (file, U"\n\n", numberOfFixedRankings, U" fixed rankings");
- for (integer irank = 1; irank <= numberOfFixedRankings; irank ++) {
- OTGrammarFixedRanking fixedRanking = & fixedRankings [irank];
- MelderFile_write (file, U"\n ", fixedRanking -> higher, U" ", fixedRanking -> lower);
- }
- MelderFile_write (file, U"\n\n", numberOfTableaus, U" tableaus");
- for (integer itab = 1; itab <= numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & tableaus [itab];
- MelderFile_write (file, U"\ninput [", itab, U"]: \"");
- for (const char32 *p = & tableau -> input [0]; *p; p ++) {
- if (*p == U'\"')
- MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
- MelderFile_writeCharacter (file, *p);
- }
- MelderFile_write (file, U"\" ", tableau -> numberOfCandidates);
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- MelderFile_write (file, U"\n candidate [", icand, U"]: \"");
- for (const char32 *p = & candidate -> output [0]; *p; p ++) {
- if (*p == U'\"')
- MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
- MelderFile_writeCharacter (file, *p);
- }
- MelderFile_writeCharacter (file, U'\"');
- for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++) {
- MelderFile_write (file, U" ", candidate -> marks [icons]);
- }
- }
- }
- }
- void OTGrammar_checkIndex (OTGrammar me) {
- if (my index) return;
- my index = NUMvector <integer> (1, my numberOfConstraints);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) my index [icons] = icons;
- OTGrammar_sort (me);
- }
- void structOTGrammar :: v_readText (MelderReadText text, int formatVersion) {
- OTGrammar_Parent :: v_readText (text, formatVersion);
- if (formatVersion >= 1) {
- try {
- decisionStrategy = (kOTGrammar_decisionStrategy) texgete8 (text, (enum_generic_getValue) kOTGrammar_decisionStrategy_getValue);
- } catch (MelderError) {
- Melder_throw (U"Trying to read decision strategy.");
- }
- }
- if (formatVersion >= 2) {
- try {
- leak = texgetr64 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read leak.");
- }
- }
- try {
- numberOfConstraints = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read number of constraints.");
- }
- if (numberOfConstraints < 1) Melder_throw (U"No constraints.");
- constraints = NUMvector <structOTGrammarConstraint> (1, numberOfConstraints);
- for (integer icons = 1; icons <= numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & constraints [icons];
- try {
- constraint -> name = texgetw16 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read name of constraint ", icons, U".");
- }
- try {
- constraint -> ranking = texgetr64 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read ranking of constraint ", icons, U".");
- }
- try {
- constraint -> disharmony = texgetr64 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read disharmony of constraint ", icons, U".");
- }
- if (formatVersion < 2) {
- constraint -> plasticity = 1.0;
- } else {
- try {
- constraint -> plasticity = texgetr64 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read plasticity of constraint ", icons, U".");
- }
- }
- }
- try {
- numberOfFixedRankings = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read number of fixed rankings.");
- }
- if (numberOfFixedRankings >= 1) {
- fixedRankings = NUMvector <structOTGrammarFixedRanking> (1, numberOfFixedRankings);
- for (integer irank = 1; irank <= numberOfFixedRankings; irank ++) {
- OTGrammarFixedRanking fixedRanking = & fixedRankings [irank];
- try {
- fixedRanking -> higher = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read the higher of constraint pair ", irank, U".");
- }
- try {
- fixedRanking -> lower = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read the lower of constraint pair ", irank, U".");
- }
- }
- }
- try {
- numberOfTableaus = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read number of tableaus.");
- }
- if (numberOfTableaus < 1) Melder_throw (U"No tableaus.");
- tableaus = NUMvector <structOTGrammarTableau> (1, numberOfTableaus);
- for (integer itab = 1; itab <= numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & tableaus [itab];
- try {
- tableau -> input = texgetw16 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read input of tableau ", itab, U".");
- }
- try {
- tableau -> numberOfCandidates = texgeti32 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read number of candidates of tableau ", itab, U".");
- }
- Melder_require (tableau -> numberOfCandidates > 0,
- U"No candidates in tableau ", itab,
- U" (input: ", tableau -> input.get(), U")"
- U" in line ", MelderReadText_getLineNumber (text),
- itab == 1 ? U"." : U", or perhaps wrong number of candidates for input «",
- itab == 1 ? nullptr : tableaus [itab - 1]. input.get(),
- itab == 1 ? nullptr : U"»."
- );
- tableau -> candidates = NUMvector <structOTGrammarCandidate> (1, tableau -> numberOfCandidates);
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- try {
- candidate -> output = texgetw16 (text);
- } catch (MelderError) {
- Melder_throw (U"Trying to read candidate ", icand, U" of tableau ", itab,
- U" (input: ", tableau -> input.get(), U") in line ", MelderReadText_getLineNumber (text), U".");
- }
- candidate -> numberOfConstraints = numberOfConstraints; // redundancy, needed for writing binary
- candidate -> marks = INTVECzero (candidate -> numberOfConstraints);
- for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++) {
- try {
- candidate -> marks [icons] = texgeti16 (text);
- } catch (MelderError) {
- Melder_throw
- (U"Trying to read number of violations of constraint ", icons,
- U" (", constraints [icons]. name.get(), U")"
- U" of candidate ", icand,
- U" (", candidate -> output.get(), U")"
- U" of tableau ", itab,
- U" (input: ", tableau -> input.get(), U")"
- U" in line ", MelderReadText_getLineNumber (text), U".");
- }
- }
- }
- }
- OTGrammar_checkIndex (this);
- }
- Thing_implement (OTGrammar, Daata, 2);
- Thing_implement (OTHistory, TableOfReal, 0);
- static OTGrammar constraintCompare_grammar;
- static int constraintCompare (const void *first, const void *second) {
- OTGrammar me = constraintCompare_grammar;
- integer icons = * (integer *) first, jcons = * (integer *) second;
- OTGrammarConstraint ci = & my constraints [icons], cj = & my constraints [jcons];
- /*
- Sort primarily by disharmony.
- */
- if (ci -> disharmony > cj -> disharmony) return -1;
- if (ci -> disharmony < cj -> disharmony) return +1;
- /*
- Tied constraints are sorted alphabetically.
- */
- return str32cmp (my constraints [icons]. name.get(), my constraints [jcons]. name.get());
- }
- void OTGrammar_sort (OTGrammar me) {
- constraintCompare_grammar = me;
- qsort (& my index [1], my numberOfConstraints, sizeof (integer), constraintCompare);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- constraint -> tiedToTheLeft = ( icons > 1 &&
- my constraints [my index [icons - 1]]. disharmony == constraint -> disharmony );
- constraint -> tiedToTheRight = ( icons < my numberOfConstraints &&
- my constraints [my index [icons + 1]]. disharmony == constraint -> disharmony );
- }
- }
- void OTGrammar_newDisharmonies (OTGrammar me, double spreading) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- constraint -> disharmony = constraint -> ranking + NUMrandomGauss (0, spreading)
- /*NUMrandomUniform (-spreading, spreading)*/;
- }
- OTGrammar_sort (me);
- }
- integer OTGrammar_getTableau (OTGrammar me, conststring32 input) {
- integer n = my numberOfTableaus;
- for (integer i = 1; i <= n; i ++)
- if (str32equ (my tableaus [i]. input.get(), input))
- return i;
- Melder_throw (U"Input \"", input, U"\" not in list of tableaus.");
- }
- static void _OTGrammar_fillInHarmonies (OTGrammar me, integer itab) noexcept {
- if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) return;
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- INTVEC marks = candidate -> marks.get();
- longdouble disharmony = 0.0;
- if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
- my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
- {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- disharmony += my constraints [icons]. disharmony * marks [icons];
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
- {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- disharmony += exp (my constraints [icons]. disharmony) * marks [icons];
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- if (my constraints [icons]. disharmony > 0.0)
- disharmony += my constraints [icons]. disharmony * marks [icons];
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- double constraintDisharmony = my constraints [icons]. disharmony > 1.0 ? my constraints [icons]. disharmony : 1.0;
- disharmony += constraintDisharmony * marks [icons];
- }
- } else {
- Melder_fatal (U"_OTGrammar_fillInHarmonies: unimplemented decision strategy.");
- }
- candidate -> harmony = - (double) disharmony;
- }
- }
- int OTGrammar_compareCandidates (OTGrammar me, integer itab1, integer icand1, integer itab2, integer icand2) noexcept {
- INTVEC marks1 = my tableaus [itab1]. candidates [icand1]. marks.get();
- INTVEC marks2 = my tableaus [itab2]. candidates [icand2]. marks.get();
- if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- integer numberOfMarks1 = marks1 [my index [icons]];
- integer numberOfMarks2 = marks2 [my index [icons]];
- /*
- Count tied constraints as one.
- */
- while (my constraints [my index [icons]]. tiedToTheRight) {
- icons ++;
- numberOfMarks1 += marks1 [my index [icons]];
- numberOfMarks2 += marks2 [my index [icons]];
- }
- if (numberOfMarks1 < numberOfMarks2) return -1; // candidate 1 is better than candidate 2
- if (numberOfMarks1 > numberOfMarks2) return +1; // candidate 2 is better than candidate 1
- }
- /* If we arrive here, None of the comparisons found a difference between the two candidates. Hence, they are equally good. */
- return 0;
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
- my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
- {
- double disharmony1 = 0.0, disharmony2 = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
- disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
- }
- if (disharmony1 < disharmony2) return -1; // candidate 1 is better than candidate 2
- if (disharmony1 > disharmony2) return +1; // candidate 2 is better than candidate 1
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
- double disharmony1 = 0.0, disharmony2 = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- if (my constraints [icons]. disharmony > 0.0) {
- disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
- disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
- }
- }
- if (disharmony1 < disharmony2) return -1; // candidate 1 is better than candidate 2
- if (disharmony1 > disharmony2) return +1; // candidate 2 is better than candidate 1
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
- {
- double disharmony1 = 0.0, disharmony2 = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- disharmony1 += exp (my constraints [icons]. disharmony) * marks1 [icons];
- disharmony2 += exp (my constraints [icons]. disharmony) * marks2 [icons];
- }
- if (disharmony1 < disharmony2) return -1; // candidate 1 is better than candidate 2
- if (disharmony1 > disharmony2) return +1; // candidate 2 is better than candidate 1
- } else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
- double disharmony1 = 0.0, disharmony2 = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- double constraintDisharmony = my constraints [icons]. disharmony > 1.0 ? my constraints [icons]. disharmony : 1.0;
- disharmony1 += constraintDisharmony * marks1 [icons];
- disharmony2 += constraintDisharmony * marks2 [icons];
- }
- if (disharmony1 < disharmony2) return -1; // candidate 1 is better than candidate 2
- if (disharmony1 > disharmony2) return +1; // candidate 2 is better than candidate 1
- } else Melder_fatal (U"Unimplemented decision strategy.");
- return 0; // the two total disharmonies are equal
- }
- static void _OTGrammar_fillInProbabilities (OTGrammar me, integer itab) noexcept {
- OTGrammarTableau tableau = & my tableaus [itab];
- double maximumHarmony = tableau -> candidates [1]. harmony;
- for (integer icand = 2; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- if (candidate -> harmony > maximumHarmony)
- maximumHarmony = candidate -> harmony;
- }
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- candidate -> probability = exp (candidate -> harmony - maximumHarmony);
- Melder_assert (candidate -> probability >= 0.0 && candidate -> probability <= 1.0);
- }
- double sumOfProbabilities = 0.0;
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- sumOfProbabilities += candidate -> probability;
- }
- Melder_assert (sumOfProbabilities > 0.0); // because at least one of them is 1.0
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- candidate -> probability /= sumOfProbabilities;
- }
- }
- integer OTGrammar_getWinner (OTGrammar me, integer itab) noexcept {
- integer icand_best = 1;
- if (my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
- {
- _OTGrammar_fillInHarmonies (me, itab);
- _OTGrammar_fillInProbabilities (me, itab);
- double cutOff = NUMrandomUniform (0.0, 1.0);
- double sumOfProbabilities = 0.0;
- for (integer icand = 1; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
- sumOfProbabilities += my tableaus [itab]. candidates [icand]. probability;
- if (sumOfProbabilities > cutOff) {
- icand_best = icand;
- break;
- }
- }
- } else {
- integer numberOfBestCandidates = 1;
- for (integer icand = 2; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
- int comparison = OTGrammar_compareCandidates (me, itab, icand, itab, icand_best);
- if (comparison == -1) {
- icand_best = icand; // the current candidate is the unique best candidate found so far
- numberOfBestCandidates = 1;
- } else if (comparison == 0) {
- numberOfBestCandidates += 1; // the current candidate is equally good as the best found before
- /*
- * Give all candidates that are equally good an equal chance to become the winner.
- */
- if (Melder_debug == 41) {
- icand_best = icand_best; // keep first
- } else if (Melder_debug == 42) {
- icand_best = icand; // take last
- } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
- icand_best = icand;
- }
- }
- }
- }
- return icand_best;
- }
- integer OTGrammar_getNumberOfOptimalCandidates (OTGrammar me, integer itab) {
- if (my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY) return 1;
- integer icand_best = 1, numberOfBestCandidates = 1;
- for (integer icand = 2; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
- int comparison = OTGrammar_compareCandidates (me, itab, icand, itab, icand_best);
- if (comparison == -1) {
- icand_best = icand; // the current candidate is the best candidate found so far
- numberOfBestCandidates = 1;
- } else if (comparison == 0) {
- numberOfBestCandidates += 1; // the current candidate is equally good as the best found before
- }
- }
- return numberOfBestCandidates;
- }
- bool OTGrammar_isCandidateGrammatical (OTGrammar me, integer itab, integer icand) {
- for (integer jcand = 1; jcand <= my tableaus [itab]. numberOfCandidates; jcand ++)
- if (jcand != icand && OTGrammar_compareCandidates (me, itab, jcand, itab, icand) < 0)
- return false;
- return true;
- }
- bool OTGrammar_isCandidateSinglyGrammatical (OTGrammar me, integer itab, integer icand) {
- for (integer jcand = 1; jcand <= my tableaus [itab]. numberOfCandidates; jcand ++)
- if (jcand != icand && OTGrammar_compareCandidates (me, itab, jcand, itab, icand) <= 0)
- return false;
- return true;
- }
- void OTGrammar_getInterpretiveParse (OTGrammar me, conststring32 partialOutput, integer *out_bestTableau, integer *out_bestCandidate) {
- try {
- integer itab_best = 0, icand_best = 0, numberOfBestCandidates = 0;
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tableau -> candidates [icand];
- if (str32str (cand -> output.get(), partialOutput)) { // T&S' idea of surface->overt mapping
- if (itab_best == 0) {
- itab_best = itab; // the first compatible input/output pair found is the first guess for the best candidate
- icand_best = icand;
- numberOfBestCandidates = 1;
- } else {
- int comparison = OTGrammar_compareCandidates (me, itab, icand, itab_best, icand_best);
- if (comparison == -1) {
- itab_best = itab; // the current input/output pair is the best candidate found so far
- icand_best = icand;
- numberOfBestCandidates = 1;
- } else if (comparison == 0) {
- numberOfBestCandidates += 1; // the current input/output pair is equally good as the best found before
- /*
- * Give all candidates that are equally good an equal chance to become the winner.
- */
- if (Melder_debug == 41) {
- itab_best = itab_best;
- icand_best = icand_best; // keep first
- } else if (Melder_debug == 42) {
- itab_best = itab;
- icand_best = icand; // take last
- } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
- itab_best = itab;
- icand_best = icand;
- }
- }
- }
- }
- }
- }
- if (itab_best == 0)
- Melder_throw (U"The partial output \"", partialOutput, U"\" does not match any candidate for any input form.");
- if (out_bestTableau)
- *out_bestTableau = itab_best;
- if (out_bestCandidate)
- *out_bestCandidate = icand_best;
- } catch (MelderError) {
- Melder_throw (U"Interpretive parse not computed.");
- }
- }
- static void OTGrammar_getInterpretiveParse_opt (OTGrammar me, integer ipartialOutput, integer *out_bestTableau, integer *out_bestCandidate) {
- try {
- integer itab_best = 0, icand_best = 0, numberOfBestCandidates = 0;
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tableau -> candidates [icand];
- Melder_assert (cand -> partialOutputMatches);
- if (cand -> partialOutputMatches [ipartialOutput]) { // T&S' idea of surface->overt mapping
- if (itab_best == 0) {
- itab_best = itab; // the first compatible input/output pair found is the first guess for the best candidate
- icand_best = icand;
- numberOfBestCandidates = 1;
- } else {
- int comparison = OTGrammar_compareCandidates (me, itab, icand, itab_best, icand_best);
- if (comparison == -1) {
- itab_best = itab; // the current input/output pair is the best candidate found so far
- icand_best = icand;
- numberOfBestCandidates = 1;
- } else if (comparison == 0) {
- numberOfBestCandidates += 1; // the current input/output pair is equally good as the best found before
- /*
- * Give all candidates that are equally good an equal chance to become the winner.
- */
- if (Melder_debug == 41) {
- itab_best = itab_best;
- icand_best = icand_best; // keep first
- } else if (Melder_debug == 42) {
- itab_best = itab;
- icand_best = icand; // take last
- } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
- itab_best = itab;
- icand_best = icand;
- }
- }
- }
- }
- }
- }
- Melder_assert (itab_best != 0);
- if (out_bestTableau)
- *out_bestTableau = itab_best;
- if (out_bestCandidate)
- *out_bestCandidate = icand_best;
- } catch (MelderError) {
- Melder_throw (U"Interpretive parse not computed.");
- }
- }
- bool OTGrammar_isPartialOutputGrammatical (OTGrammar me, conststring32 partialOutput) {
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- if (str32str (tableau -> candidates [icand]. output.get(), partialOutput))
- if (OTGrammar_isCandidateGrammatical (me, itab, icand))
- return true;
- }
- }
- return false;
- }
- bool OTGrammar_areAllPartialOutputsGrammatical (OTGrammar me, Strings thee) {
- for (integer ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
- conststring32 partialOutput = thy strings [ioutput].get();
- if (! OTGrammar_isPartialOutputGrammatical (me, partialOutput))
- return false;
- }
- return true;
- }
- bool OTGrammar_isPartialOutputSinglyGrammatical (OTGrammar me, conststring32 partialOutput) {
- bool found = false;
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- if (str32str (tableau -> candidates [icand]. output.get(), partialOutput)) {
- if (OTGrammar_isCandidateGrammatical (me, itab, icand)) {
- found = true;
- /*
- All other grammatical candidates should match.
- */
- for (integer jcand = 1; jcand <= tableau -> numberOfCandidates; jcand ++) {
- if (OTGrammar_compareCandidates (me, itab, jcand, itab, icand) == 0)
- if (! str32str (tableau -> candidates [jcand]. output.get(), partialOutput))
- return false; // partial output is multiply optimal
- }
- }
- }
- }
- }
- return found;
- }
- bool OTGrammar_areAllPartialOutputsSinglyGrammatical (OTGrammar me, Strings thee) {
- for (integer ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
- conststring32 partialOutput = thy strings [ioutput].get();
- if (! OTGrammar_isPartialOutputSinglyGrammatical (me, partialOutput))
- return false;
- }
- return true;
- }
- static integer OTGrammar_crucialCell (OTGrammar me, integer itab, integer icand, integer iwinner, integer numberOfOptimalCandidates) {
- OTGrammarTableau tableau = & my tableaus [itab];
- if (tableau -> numberOfCandidates < 2) return 0; // if there is only one candidate, all cells can be greyed
- if (my decisionStrategy != kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) return my numberOfConstraints; // nothing grey
- if (OTGrammar_compareCandidates (me, itab, icand, itab, iwinner) == 0) { // candidate equally good as winner?
- if (numberOfOptimalCandidates > 1) {
- /* All cells are important. */
- } else {
- integer secondBest = 0;
- for (integer jcand = 1; jcand <= tableau -> numberOfCandidates; jcand ++) {
- if (OTGrammar_compareCandidates (me, itab, jcand, itab, iwinner) != 0) { // a non-optimal candidate?
- if (secondBest == 0) {
- secondBest = jcand; // first guess
- } else if (OTGrammar_compareCandidates (me, itab, jcand, itab, secondBest) < 0) {
- secondBest = jcand; // better guess
- }
- }
- }
- if (secondBest == 0) return 0; // if all candidates are equally good, all cells can be greyed
- return OTGrammar_crucialCell (me, itab, secondBest, iwinner, 1);
- }
- } else {
- const constINTVEC candidateMarks = tableau -> candidates [icand]. marks.get();
- const constINTVEC winnerMarks = tableau -> candidates [iwinner]. marks.get();
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- integer numberOfCandidateMarks = candidateMarks [my index [icons]];
- integer numberOfWinnerMarks = winnerMarks [my index [icons]];
- while (my constraints [my index [icons]]. tiedToTheRight) {
- icons ++;
- numberOfCandidateMarks += candidateMarks [my index [icons]];
- numberOfWinnerMarks += winnerMarks [my index [icons]];
- }
- if (numberOfCandidateMarks > numberOfWinnerMarks)
- return icons;
- }
- }
- return my numberOfConstraints; // nothing grey
- }
- static double OTGrammar_constraintWidth (Graphics g, conststring32 name) {
- char32 text [100];
- str32cpy (text, name);
- char32 *newLine = str32chr (text, U'\n');
- if (newLine) {
- double firstWidth, secondWidth;
- *newLine = U'\0';
- firstWidth = Graphics_textWidth (g, text);
- secondWidth = Graphics_textWidth (g, newLine + 1);
- return firstWidth > secondWidth ? firstWidth : secondWidth;
- }
- return Graphics_textWidth (g, text);
- }
- void OTGrammar_drawTableau (OTGrammar me, Graphics g, bool vertical, conststring32 input) {
- try {
- const double fontSize = Graphics_inqFontSize (g);
- Graphics_Colour colour = Graphics_inqColour (g);
- const integer itab = OTGrammar_getTableau (me, input);
- _OTGrammar_fillInHarmonies (me, itab);
- const integer winner = OTGrammar_getWinner (me, itab);
-
- Graphics_setWindow (g, 0.0, 1.0, 0.0, 1.0);
- const double margin = Graphics_dxMMtoWC (g, 1.0);
- const double fingerWidth = Graphics_dxMMtoWC (g, 7.0) * fontSize / 12.0;
- const double doubleLineDx = Graphics_dxMMtoWC (g, 0.9);
- const double doubleLineDy = Graphics_dyMMtoWC (g, 0.9);
- const double rowHeight = Graphics_dyMMtoWC (g, 1.5 * fontSize * 25.4 / 72);
- const double descent = rowHeight * 0.5;
- const double worldAspectRatio = Graphics_dyMMtoWC (g, 1.0) / Graphics_dxMMtoWC (g, 1.0); // because Graphics_textWidth measures in the x direction only
- /*
- Compute the height of the header row.
- */
- double headerHeight;
- if (vertical) {
- headerHeight = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- const double constraintTextWidth = Graphics_textWidth (g, constraint -> name.get());
- if (constraintTextWidth > headerHeight)
- headerHeight = constraintTextWidth;
- }
- headerHeight += margin * 2;
- headerHeight *= worldAspectRatio;
- } else {
- headerHeight = rowHeight;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- if (str32chr (constraint -> name.get(), U'\n')) {
- headerHeight *= 1.6;
- break;
- }
- }
- }
- /*
- Compute longest candidate string.
- Also count the number of optimal candidates (if there are more than one, the fingers will be drawn in red).
- */
- double candWidth = Graphics_textWidth (g, input);
- OTGrammarTableau tableau = & my tableaus [itab];
- integer numberOfOptimalCandidates = 0;
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- double width = Graphics_textWidth (g, tableau -> candidates [icand]. output.get());
- if (OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0) {
- width += fingerWidth;
- numberOfOptimalCandidates ++;
- }
- if (width > candWidth) candWidth = width;
- }
- candWidth += margin * 3;
- /*
- Compute tableau width.
- */
- double tableauWidth = candWidth + doubleLineDx;
- if (vertical) {
- tableauWidth += rowHeight * my numberOfConstraints / worldAspectRatio;
- } else {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- tableauWidth += OTGrammar_constraintWidth (g, constraint -> name.get());
- }
- tableauWidth += margin * 2 * my numberOfConstraints;
- }
- /*
- Draw box.
- */
- double x = doubleLineDx; // left side of tableau
- double y = 1.0 - doubleLineDy;
- Graphics_rectangle (g, x, x + tableauWidth,
- y - headerHeight - tableau -> numberOfCandidates * rowHeight - doubleLineDy, y);
- /*
- Draw input.
- */
- y -= headerHeight;
- Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
- Graphics_text (g, x + 0.5 * candWidth, y + 0.5 * headerHeight, input);
- Graphics_rectangle (g, x, x + candWidth, y, y + headerHeight);
- /*
- Draw constraint names.
- */
- x += candWidth + doubleLineDx;
- if (vertical)
- Graphics_setTextRotation (g, 90.0);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- double width = vertical ? rowHeight / worldAspectRatio : OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2;
- if (str32chr (constraint -> name.get(), U'\n') && ! vertical) {
- autoMelderString text;
- MelderString_copy (& text, constraint -> name.get());
- char32 *newLine = str32chr (text.string, U'\n');
- *newLine = U'\0';
- Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_TOP);
- Graphics_text (g, x + 0.5 * width, y + headerHeight, text.string);
- Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
- Graphics_text (g, x + 0.5 * width, y, newLine + 1);
- } else if (vertical) {
- Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
- Graphics_text (g, x + 0.5 * width, y + margin, constraint -> name.get());
- } else {
- Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
- Graphics_text (g, x + 0.5 * width, y + 0.5 * headerHeight, constraint -> name.get());
- }
- if (constraint -> tiedToTheLeft)
- Graphics_setLineType (g, Graphics_DOTTED);
- Graphics_line (g, x, y, x, y + headerHeight);
- Graphics_setLineType (g, Graphics_DRAWN);
- Graphics_line (g, x, y, x + width, y);
- x += width;
- }
- if (vertical) Graphics_setTextRotation (g, 0.0);
- /*
- Draw candidates.
- */
- y -= doubleLineDy;
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- integer crucialCell = OTGrammar_crucialCell (me, itab, icand, winner, numberOfOptimalCandidates);
- bool candidateIsOptimal = OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0;
- /*
- Draw candidate transcription.
- */
- x = doubleLineDx;
- y -= rowHeight;
- Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
- Graphics_text (g, x + candWidth - margin, y + descent, tableau -> candidates [icand]. output.get());
- if (candidateIsOptimal) {
- Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
- Graphics_setFontSize (g, (int) (1.5 * fontSize));
- if (numberOfOptimalCandidates > 1) Graphics_setColour (g, Graphics_RED);
- Graphics_text (g, x + margin, y + descent - Graphics_dyMMtoWC (g, 1.0) * fontSize / 12.0, U"☞");
- Graphics_setColour (g, colour);
- Graphics_setFontSize (g, (int) fontSize);
- }
- Graphics_rectangle (g, x, x + candWidth, y, y + rowHeight);
- /*
- Draw grey cell backgrounds.
- */
- x = candWidth + 2 * doubleLineDx;
- Graphics_setGrey (g, 0.9);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer index = my index [icons];
- OTGrammarConstraint constraint = & my constraints [index];
- const double width = ( vertical ? rowHeight / worldAspectRatio :
- OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2 );
- if (icons > crucialCell)
- Graphics_fillRectangle (g, x, x + width, y, y + rowHeight);
- x += width;
- }
- Graphics_setColour (g, colour);
- /*
- Draw cell marks.
- */
- x = candWidth + 2 * doubleLineDx;
- Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer index = my index [icons];
- OTGrammarConstraint constraint = & my constraints [index];
- const double width = vertical ? rowHeight / worldAspectRatio : OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2;
- static MelderString markString;
- MelderString_empty (& markString);
- if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
- /*
- An exclamation mark can be drawn in this cell only if all of the following conditions are met:
- 1. the candidate is not optimal;
- 2. the constraint is not tied;
- 3. this is the crucial cell, i.e. the cells after it are drawn in grey.
- */
- if (icons == crucialCell && ! candidateIsOptimal && ! constraint -> tiedToTheLeft && ! constraint -> tiedToTheRight) {
- const integer winnerMarks = tableau -> candidates [winner]. marks [index];
- for (integer imark = 1; imark <= winnerMarks + 1; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
- MelderString_appendCharacter (& markString, U'+');
- MelderString_appendCharacter (& markString, U'!');
- for (integer imark = winnerMarks + 2; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- } else {
- if (! candidateIsOptimal && (constraint -> tiedToTheLeft || constraint -> tiedToTheRight) &&
- crucialCell >= 1 && constraint -> disharmony == my constraints [my index [crucialCell]]. disharmony)
- {
- Graphics_setColour (g, Graphics_RED);
- }
- for (integer imark = 1; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
- MelderString_appendCharacter (& markString, U'+');
- }
- } else {
- for (integer imark = 1; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
- MelderString_appendCharacter (& markString, U'+');
- }
- Graphics_text (g, x + 0.5 * width, y + descent, markString.string);
- Graphics_setColour (g, colour);
- if (constraint -> tiedToTheLeft)
- Graphics_setLineType (g, Graphics_DOTTED);
- Graphics_line (g, x, y, x, y + rowHeight);
- Graphics_setLineType (g, Graphics_DRAWN);
- Graphics_line (g, x, y + rowHeight, x + width, y + rowHeight);
- x += width;
- }
- /*
- Draw harmony.
- */
- if (my decisionStrategy != kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
- Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
- const double value = tableau -> candidates [icand]. harmony;
- if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
- {
- //value = value > 1e-308 ? 1000 : value < -1e308 ? -1000 : - log (- value);
- Graphics_text (g, x, y + descent, Melder_float (Melder_half (value)));
- } else {
- Graphics_text (g, x, y + descent, Melder_fixed (value, 3));
- }
- }
- }
- /*
- Draw box.
- */
- x = doubleLineDx; // left side of tableau
- y = 1.0 - doubleLineDy;
- Graphics_rectangle (g, x, x + tableauWidth,
- y - headerHeight - tableau -> numberOfCandidates * rowHeight - doubleLineDy, y);
- } catch (MelderError) {
- Melder_throw (me, U": tableau not drawn.");
- }
- }
- autoStrings OTGrammar_generateInputs (OTGrammar me, integer numberOfTrials) {
- try {
- autoStrings thee = Thing_new (Strings);
- thy strings = autostring32vector (thy numberOfStrings = numberOfTrials);
- for (integer i = 1; i <= numberOfTrials; i ++) {
- integer itab = NUMrandomInteger (1, my numberOfTableaus);
- thy strings [i] = Melder_dup (my tableaus [itab]. input.get());
- }
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": inputs not generated.");
- }
- }
- autoStrings OTGrammar_getInputs (OTGrammar me) {
- try {
- autoStrings thee = Thing_new (Strings);
- thy strings = autostring32vector (thy numberOfStrings = my numberOfTableaus);
- for (integer i = 1; i <= my numberOfTableaus; i ++)
- thy strings [i] = Melder_dup (my tableaus [i]. input.get());
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": inputs not gotten.");
- }
- }
- autostring32 OTGrammar_inputToOutput (OTGrammar me, conststring32 input, double evaluationNoise) {
- try {
- OTGrammar_newDisharmonies (me, evaluationNoise);
- integer itab = OTGrammar_getTableau (me, input);
- integer winner = OTGrammar_getWinner (me, itab);
- if (winner == 0)
- Melder_throw (U"No winner");
- return Melder_dup (my tableaus [itab]. candidates [winner]. output.get());
- } catch (MelderError) {
- Melder_throw (me, U": output not computed from input \"", input, U"\".");
- }
- }
- autoStrings OTGrammar_inputsToOutputs (OTGrammar me, Strings inputs, double evaluationNoise) {
- try {
- autoStrings him = Thing_new (Strings);
- integer n = inputs -> numberOfStrings;
- his numberOfStrings = n;
- his strings = autostring32vector (n);
- for (integer i = 1; i <= n; i ++)
- his strings [i] = OTGrammar_inputToOutput (me, inputs -> strings [i].get(), evaluationNoise);
- return him;
- } catch (MelderError) {
- Melder_throw (me, U": outputs not computed.");
- }
- }
- autoStrings OTGrammar_inputToOutputs (OTGrammar me, conststring32 input, integer n, double evaluationNoise) {
- try {
- autoStrings thee = Thing_new (Strings);
- thy numberOfStrings = n;
- thy strings = autostring32vector (n);
- for (integer i = 1; i <= n; i ++)
- thy strings [i] = OTGrammar_inputToOutput (me, input, evaluationNoise);
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": output not computed.");
- }
- }
- autoDistributions OTGrammar_to_Distribution (OTGrammar me, integer trialsPerInput, double noise) {
- try {
- integer totalNumberOfOutputs = 0, nout = 0;
- /*
- Count the total number of outputs.
- */
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++)
- totalNumberOfOutputs += my tableaus [itab]. numberOfCandidates;
- /*
- Create the distribution. One row for every output form.
- */
- autoDistributions thee = Distributions_create (totalNumberOfOutputs, 1);
- /*
- Measure every input form.
- */
- autoMelderProgress progress (U"OTGrammar: compute output distribution.");
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
- /*
- Set the row labels to the output strings.
- */
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- thy rowLabels [nout + icand] = Melder_dup (Melder_cat (tableau -> input.get(), U" \\-> ", tableau -> candidates [icand]. output.get()));
- }
- /*
- Compute a number of outputs and store the results.
- */
- for (integer itrial = 1; itrial <= trialsPerInput; itrial ++) {
- OTGrammar_newDisharmonies (me, noise);
- integer iwinner = OTGrammar_getWinner (me, itab);
- thy data [nout + iwinner] [1] += 1;
- }
- /*
- Update the offset.
- */
- nout += tableau -> numberOfCandidates;
- }
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": output distribution not computed.");
- }
- }
- autoPairDistribution OTGrammar_to_PairDistribution (OTGrammar me, integer trialsPerInput, double noise) {
- try {
- integer nout = 0;
- /*
- Create the distribution. One row for every output form.
- */
- autoPairDistribution thee = PairDistribution_create ();
- /*
- Measure every input form.
- */
- autoMelderProgress progress (U"OTGrammar: compute output distribution.");
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
- /*
- Copy the input and output strings to the target object.
- */
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- PairDistribution_add (thee.get(), tableau -> input.get(), tableau -> candidates [icand]. output.get(), 0.0);
- }
- /*
- Compute a number of outputs and store the results.
- */
- for (integer itrial = 1; itrial <= trialsPerInput; itrial ++) {
- OTGrammar_newDisharmonies (me, noise);
- integer iwinner = OTGrammar_getWinner (me, itab);
- thy pairs.at [nout + iwinner] -> weight += 1.0;
- }
- /*
- Update the offset.
- */
- nout += tableau -> numberOfCandidates;
- }
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": output distribution not computed.");
- }
- }
- static bool honoursFixedRankings (OTGrammar me) {
- for (integer i = 1; i <= my numberOfFixedRankings; i ++) {
- integer higher = my fixedRankings [i]. higher, lower = my fixedRankings [i]. lower;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- if (my index [icons] == higher) break; // detected higher before lower: OK
- if (my index [icons] == lower) return false;
- }
- }
- return true;
- }
- autoDistributions OTGrammar_measureTypology_WEAK (OTGrammar me) {
- try {
- integer totalNumberOfOutputs = 0, nout = 0, ncons = my numberOfConstraints, nperm, factorial [1+12];
- if (ncons > 12)
- Melder_throw (U"Cannot handle more than 12 constraints.");
- factorial [0] = 1;
- for (integer icons = 1; icons <= ncons; icons ++)
- factorial [icons] = factorial [icons - 1] * icons;
- nperm = factorial [ncons];
- /*
- Count the total number of outputs.
- */
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++)
- totalNumberOfOutputs += my tableaus [itab]. numberOfCandidates;
- /*
- Create the distribution. One row for every output form.
- */
- autoDistributions thee = Distributions_create (totalNumberOfOutputs, 1);
- /*
- Measure every input form.
- */
- autoMelderProgress progress (U"Measuring typology...");
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
- /*
- Set the row labels to the output strings.
- */
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- thy rowLabels [nout + icand] = Melder_dup (Melder_cat (tableau -> input.get(), U" \\-> ", tableau -> candidates [icand]. output.get()));
- }
- /*
- Compute a number of outputs and store the results.
- */
- for (integer iperm = 0; iperm < nperm; iperm ++) {
- integer permleft = iperm, iwinner;
- /* Initialize to 12345 before permuting. */
- for (integer icons = 1; icons <= ncons; icons ++)
- my index [icons] = icons;
- for (integer icons = 1; icons < ncons; icons ++) {
- integer fac = factorial [ncons - icons], shift = permleft / fac, dummy;
- /*
- Swap constraint with the one at a distance 'shift'.
- */
- dummy = my index [icons];
- my index [icons] = my index [icons + shift];
- my index [icons + shift] = dummy;
- permleft %= fac;
- }
- if (honoursFixedRankings (me)) {
- iwinner = OTGrammar_getWinner (me, itab);
- thy data [nout + iwinner] [1] += 1;
- }
- }
- /*
- Update the offset.
- */
- nout += tableau -> numberOfCandidates;
- }
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": typology not measured.");
- }
- }
- static double learningStep (double mean, double relativeSpreading) {
- return relativeSpreading == 0.0 ? mean : NUMrandomGauss (mean, relativeSpreading * mean);
- }
- static void OTGrammar_honourLocalRankings (OTGrammar me, double plasticity, double relativePlasticityNoise, bool *grammarHasChanged) {
- bool improved;
- do {
- improved = false;
- for (integer irank = 1; irank <= my numberOfFixedRankings; irank ++) {
- OTGrammarFixedRanking fixedRanking = & my fixedRankings [irank];
- OTGrammarConstraint higher = & my constraints [fixedRanking -> higher], lower = & my constraints [fixedRanking -> lower];
- while (higher -> ranking <= lower -> ranking) {
- lower -> ranking -= learningStep (plasticity, relativePlasticityNoise);
- if (grammarHasChanged)
- *grammarHasChanged = true;
- improved = true;
- }
- }
- } while (improved);
- }
- static void OTGrammar_modifyRankings (OTGrammar me, integer itab, integer iwinner, integer iadult,
- kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, bool warnIfStalled, bool *out_grammarHasChanged)
- {
- try {
- OTGrammarTableau tableau = & my tableaus [itab];
- OTGrammarCandidate winner = & tableau -> candidates [iwinner], adult = & tableau -> candidates [iadult];
- double step = learningStep (plasticity, relativePlasticityNoise);
- bool multiplyStepByNumberOfViolations =
- my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
- my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT ||
- my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
- my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY;
- if (Melder_debug != 0) {
- /*
- * Perhaps override the standard update rule.
- */
- if (Melder_debug == 26) multiplyStepByNumberOfViolations = false; // OT-GLA
- else if (Melder_debug == 27) multiplyStepByNumberOfViolations = true; // HG-GLA
- }
- if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ONE) {
- const integer icons = NUMrandomInteger (1, my numberOfConstraints);
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL) {
- bool changed = false;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
- changed = true;
- }
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
- changed = true;
- }
- }
- if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG)
- {
- longdouble sumOfWeights = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- sumOfWeights += my constraints [icons]. ranking;
- const double averageWeight = (double) sumOfWeights / my numberOfConstraints;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- my constraints [icons]. ranking -= averageWeight;
- }
- if (out_grammarHasChanged) *out_grammarHasChanged = changed;
- } else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL_SKIPPABLE) {
- bool changed = false;
- integer winningConstraints = 0, adultConstraints = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks)
- adultConstraints ++;
- if (winnerMarks > adultMarks)
- winningConstraints ++;
- }
- if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
- changed = true;
- }
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
- changed = true;
- }
- }
- if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG) {
- longdouble sumOfWeights = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- sumOfWeights += my constraints [icons]. ranking;
- const double averageWeight = (double) sumOfWeights / my numberOfConstraints;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- my constraints [icons]. ranking -= averageWeight;
- }
- if (out_grammarHasChanged) *out_grammarHasChanged = changed;
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_UNCANCELLED) {
- integer winningConstraints = 0, adultConstraints = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks)
- adultConstraints ++;
- if (winnerMarks > adultMarks)
- winningConstraints ++;
- }
- if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
- //constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) * winningConstraints;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
- //constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * adultConstraints;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL) {
- integer winningConstraints = 0, adultConstraints = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > 0)
- adultConstraints ++;
- if (winnerMarks > 0)
- winningConstraints ++;
- }
- if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > 0) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= adultMarks /*- winnerMarks*/;
- constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- if (winnerMarks > 0) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks /*- adultMarks*/;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::EDCD || updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
- /*
- Determine the crucial winner mark.
- */
- double pivotRanking;
- bool equivalent = true;
- integer icons = 1;
- for (; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
- const integer adultMarks = adult -> marks [my index [icons]];
- if (adultMarks < winnerMarks)
- break;
- if (adultMarks > winnerMarks)
- equivalent = false;
- }
- if (icons > my numberOfConstraints) { // completed the loop?
- if (warnIfStalled && ! equivalent)
- Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
- U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
- return; // Tesar & Smolensky (2000: 67): "stopped dead in its tracks"
- }
- /*
- Determine the stratum into which some constraints will be demoted.
- */
- pivotRanking = my constraints [my index [icons]]. ranking;
- if (updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
- integer numberOfConstraintsToDemote = 0;
- for (icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (adultMarks > winnerMarks) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- if (constraint -> ranking >= pivotRanking)
- numberOfConstraintsToDemote += 1;
- }
- }
- if (numberOfConstraintsToDemote > 0) {
- for (icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- if (constraint -> ranking < pivotRanking) {
- constraint -> ranking -= numberOfConstraintsToDemote * step * constraint -> plasticity;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- }
- }
- /*
- Demote all the uniquely violated constraints in the adult form
- that have rankings not lower than the pivot.
- */
- for (icons = 1; icons <= my numberOfConstraints; icons ++) {
- integer numberOfConstraintsDemoted = 0;
- const integer winnerMarks = winner -> marks [my index [icons]]; // for the vacation version, the order is important, therefore indirect
- const integer adultMarks = adult -> marks [my index [icons]];
- if (adultMarks > winnerMarks) {
- const OTGrammarConstraint constraint = & my constraints [my index [icons]];
- const double constraintStep = step * constraint -> plasticity;
- if (constraint -> ranking >= pivotRanking) {
- numberOfConstraintsDemoted += 1;
- constraint -> ranking = pivotRanking - numberOfConstraintsDemoted * constraintStep; // this preserves the order of the demotees
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::DEMOTION_ONLY) {
- /*
- Determine the crucial adult mark.
- */
- integer crucialAdultMark;
- OTGrammarConstraint offendingConstraint;
- integer icons = 1;
- for (; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, so we indirect
- const integer adultMarks = adult -> marks [my index [icons]];
- if (my constraints [my index [icons]]. tiedToTheRight)
- Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
- if (adultMarks < winnerMarks)
- Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
- if (adultMarks > winnerMarks)
- break;
- }
- if (icons > my numberOfConstraints) // completed the loop?
- Melder_throw (U"Adult form equals correct candidate.");
- crucialAdultMark = icons;
- /*
- Demote the highest uniquely violated constraint in the adult form.
- */
- offendingConstraint = & my constraints [my index [crucialAdultMark]];
- double constraintStep = step * offendingConstraint -> plasticity;
- offendingConstraint -> ranking -= constraintStep;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN) {
- integer numberOfUp = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (winnerMarks > adultMarks)
- numberOfUp ++;
- }
- if (numberOfUp > 0) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / numberOfUp;
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- integer winnerMarks = 0, adultMarks = 0;
- integer icons = 1;
- for (; icons <= my numberOfConstraints; icons ++) {
- winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
- adultMarks = adult -> marks [my index [icons]];
- if (my constraints [my index [icons]]. tiedToTheRight)
- Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
- if (adultMarks < winnerMarks)
- Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
- if (adultMarks > winnerMarks) break;
- }
- if (icons > my numberOfConstraints) // completed the loop?
- Melder_throw (U"Adult form equals correct candidate.");
- const integer crucialAdultMark = icons;
- /*
- Demote the highest uniquely violated constraint in the adult form.
- */
- const OTGrammarConstraint offendingConstraint = & my constraints [my index [crucialAdultMark]];
- double constraintStep = step * offendingConstraint -> plasticity;
- if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
- offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
- if (out_grammarHasChanged) *out_grammarHasChanged = true;
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN_2012) {
- integer numberOfUp = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (winnerMarks > adultMarks)
- numberOfUp ++;
- }
- if (numberOfUp > 0) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const OTGrammarConstraint constraint = & my constraints [icons];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [icons];
- const integer adultMarks = adult -> marks [icons];
- if (winnerMarks > adultMarks) {
- if (multiplyStepByNumberOfViolations)
- constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / (numberOfUp + 1);
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- integer winnerMarks = 0, adultMarks = 0;
- integer icons = 1;
- for (; icons <= my numberOfConstraints; icons ++) {
- winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
- adultMarks = adult -> marks [my index [icons]];
- if (my constraints [my index [icons]]. tiedToTheRight)
- Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
- if (adultMarks < winnerMarks)
- Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
- if (adultMarks > winnerMarks) break;
- }
- if (icons > my numberOfConstraints) // completed the loop?
- Melder_throw (U"Adult form equals correct candidate.");
- const integer crucialAdultMark = icons;
- /*
- Demote the highest uniquely violated constraint in the adult form.
- */
- const OTGrammarConstraint offendingConstraint = & my constraints [my index [crucialAdultMark]];
- double constraintStep = step * offendingConstraint -> plasticity;
- if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
- offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
- if (out_grammarHasChanged) *out_grammarHasChanged = true;
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN) {
- integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
- const integer adultMarks = adult -> marks [my index [icons]];
- if (adultMarks < winnerMarks) {
- numberOfUp ++;
- } else if (adultMarks > winnerMarks) {
- if (numberOfUp == 0) {
- numberOfDown ++;
- lowestDemotableConstraint = icons;
- }
- }
- }
- if (warnIfStalled && numberOfDown == 0) {
- Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
- U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
- return;
- }
- if (numberOfUp > 0) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer constraintIndex = my index [icons];
- if (my constraints [constraintIndex]. tiedToTheRight)
- Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
- const OTGrammarConstraint constraint = & my constraints [constraintIndex];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [constraintIndex]; // the order is important, therefore indirect
- const integer adultMarks = adult -> marks [constraintIndex];
- if (adultMarks < winnerMarks) {
- if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 0.0);
- } else if (adultMarks > winnerMarks) {
- if (icons <= lowestDemotableConstraint) {
- if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
- }
- }
- }
- if (out_grammarHasChanged) *out_grammarHasChanged = true;
- }
- } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN_2012) {
- integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
- const integer adultMarks = adult -> marks [my index [icons]];
- if (adultMarks < winnerMarks) {
- numberOfUp ++;
- } else if (adultMarks > winnerMarks) {
- if (numberOfUp == 0) {
- numberOfDown ++;
- lowestDemotableConstraint = icons;
- }
- }
- }
- if (warnIfStalled && numberOfDown == 0) {
- Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
- U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
- return;
- }
- if (numberOfUp > 0) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- const integer constraintIndex = my index [icons];
- if (my constraints [constraintIndex]. tiedToTheRight)
- Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
- const OTGrammarConstraint constraint = & my constraints [constraintIndex];
- double constraintStep = step * constraint -> plasticity;
- const integer winnerMarks = winner -> marks [constraintIndex]; // the order is important, therefore indirect
- const integer adultMarks = adult -> marks [constraintIndex];
- if (adultMarks < winnerMarks) {
- if (multiplyStepByNumberOfViolations) constraintStep *= winnerMarks - adultMarks;
- constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 1.0);
- } else if (adultMarks > winnerMarks) {
- if (icons <= lowestDemotableConstraint) {
- if (multiplyStepByNumberOfViolations) constraintStep *= adultMarks - winnerMarks;
- constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
- }
- }
- }
- if (out_grammarHasChanged)
- *out_grammarHasChanged = true;
- }
- }
- if (honourLocalRankings && my numberOfFixedRankings)
- OTGrammar_honourLocalRankings (me, plasticity, relativePlasticityNoise, out_grammarHasChanged);
- } catch (MelderError) {
- Melder_throw (me, U": rankings not modified.");
- }
- }
- void OTGrammar_learnOne (OTGrammar me, conststring32 input, conststring32 adultOutput,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, bool newDisharmonies, bool warnIfStalled, bool *out_grammarHasChanged)
- {
- try {
- if (newDisharmonies) OTGrammar_newDisharmonies (me, evaluationNoise);
- if (out_grammarHasChanged) *out_grammarHasChanged = false;
- /*
- Evaluate the input in the learner's hypothesis.
- */
- integer itab = OTGrammar_getTableau (me, input);
- OTGrammarTableau tableau = & my tableaus [itab];
- /*
- Determine the "winner", i.e. the candidate that wins in the learner's grammar
- (Tesar & Smolensky call this the "loser").
- */
- integer iwinner = OTGrammar_getWinner (me, itab);
- OTGrammarCandidate winner = & tableau -> candidates [iwinner];
- /*
- Error-driven: compare the adult winner (the correct candidate) and the learner's winner.
- */
- if (str32equ (winner -> output.get(), adultOutput)) return; // as far as we know, the grammar is already correct: don't update rankings
- /*
- Find (perhaps the learner's interpretation of) the adult output in the learner's own tableau
- (Tesar & Smolensky call this the "winner").
- */
- integer iadult = 1;
- for (; iadult <= tableau -> numberOfCandidates; iadult ++) {
- OTGrammarCandidate cand = & tableau -> candidates [iadult];
- if (str32equ (cand -> output.get(), adultOutput)) break;
- }
- if (iadult > tableau -> numberOfCandidates)
- Melder_throw (U"Cannot generate adult output \"", adultOutput, U"\".");
- /*
- Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
- The grammar will have to change.
- */
- OTGrammar_modifyRankings (me, itab, iwinner, iadult, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, warnIfStalled, out_grammarHasChanged);
- } catch (MelderError) {
- Melder_throw (me, U": not learned from input \"", input, U"\" and adult output \"", adultOutput, U"\".");
- }
- }
- void OTGrammar_learn (OTGrammar me, Strings inputs, Strings outputs,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, integer numberOfChews)
- {
- if (! inputs) inputs = outputs;
- try {
- integer n = inputs -> numberOfStrings;
- if (outputs -> numberOfStrings != n)
- Melder_throw (U"Numbers of strings in input and output are not equal.");
- for (integer i = 1; i <= n; i ++) {
- for (integer ichew = 1; ichew <= numberOfChews; ichew ++) {
- OTGrammar_learnOne (me, inputs -> strings [i].get(), outputs -> strings [i].get(),
- evaluationNoise, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, true, true, nullptr);
- }
- }
- } catch (MelderError) {
- Melder_throw (me, U": not learned from ", inputs, U" (inputs) and ", outputs, U" (outputs).");
- }
- }
- void OTGrammar_PairDistribution_learn (OTGrammar me, PairDistribution thee,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double initialPlasticity, integer replicationsPerPlasticity, double plasticityDecrement,
- integer numberOfPlasticities, double relativePlasticityNoise, integer numberOfChews)
- {
- integer idatum = 0, numberOfData = numberOfPlasticities * replicationsPerPlasticity;
- try {
- double plasticity = initialPlasticity;
- autoMelderMonitor monitor (U"Learning with full knowledge...");
- if (monitor.graphics()) {
- Graphics_clearWs (monitor.graphics());
- }
- for (integer iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
- for (integer ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
- conststring32 input, output;
- PairDistribution_peekPair (thee, & input, & output);
- ++ idatum;
- if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
- Graphics_beginMovieFrame (monitor.graphics(), nullptr);
- Graphics_setWindow (monitor.graphics(), 0, numberOfData, 50, 150);
- for (integer icons = 1; icons <= 14 && icons <= my numberOfConstraints; icons ++) {
- Graphics_setGrey (monitor.graphics(), (double) icons / 14);
- Graphics_line (monitor.graphics(), idatum, my constraints [icons]. ranking,
- idatum, my constraints [icons]. ranking+1);
- }
- Graphics_endMovieFrame (monitor.graphics(), 0.0);
- }
- Melder_monitor ((double) idatum / numberOfData,
- U"Processing input-output pair ", idatum,
- U" out of ", numberOfData, U": ", input, U" -> ", output);
- for (integer ichew = 1; ichew <= numberOfChews; ichew ++) {
- OTGrammar_learnOne (me, input, output,
- evaluationNoise, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, true, true, nullptr);
- }
- }
- plasticity *= plasticityDecrement;
- }
- } catch (MelderError) {
- if (idatum > 1)
- Melder_appendError (U"Only ", idatum - 1, U" input-output pairs out of ", numberOfData, U" were processed.");
- Melder_throw (me, U": did not complete learning from ", thee, U".");
- }
- }
- static integer PairDistribution_getNumberOfAttestedOutputs (PairDistribution me, conststring32 input, conststring32 *out_attestedOutput) {
- integer result = 0;
- for (integer ipair = 1; ipair <= my pairs.size; ipair ++) {
- PairProbability pair = my pairs.at [ipair];
- if (str32equ (pair -> string1.get(), input) && pair -> weight > 0.0) {
- if (out_attestedOutput) *out_attestedOutput = pair -> string2.get();
- result ++;
- }
- }
- return result;
- }
- bool OTGrammar_PairDistribution_findPositiveWeights (OTGrammar me, PairDistribution thee, double weightFloor, double marginOfSeparation) {
- NUMlinprog linprog = nullptr;
- try {
- bool result = false;
- if (my decisionStrategy != kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR &&
- my decisionStrategy != kOTGrammar_decisionStrategy::LINEAR_OT &&
- my decisionStrategy != kOTGrammar_decisionStrategy::POSITIVE_HG &&
- my decisionStrategy != kOTGrammar_decisionStrategy::EXPONENTIAL_HG)
- {
- Melder_throw (U"To find positive weights, the decision strategy has to be HarmonicGrammar, LinearOT, PositiveHG, or ExponentialHG.");
- }
- autoINTVEC optimalCandidates = INTVECraw (my numberOfTableaus);
- /*
- Check that there is exactly one optimal output for each input.
- */
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- conststring32 attestedOutput = nullptr;
- integer numberOfAttestedOutputs = PairDistribution_getNumberOfAttestedOutputs (thee, tab -> input.get(), & attestedOutput);
- if (numberOfAttestedOutputs == 0) {
- Melder_throw (U"Input \"", tab -> input.get(), U"\" has no attested output.");
- } else if (numberOfAttestedOutputs > 1) {
- Melder_throw (U"Input \"", tab -> input.get(), U"\" has more than one attested output.");
- } else {
- Melder_assert (attestedOutput);
- for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tab -> candidates [icand];
- if (str32equ (attestedOutput, cand -> output.get()))
- optimalCandidates [itab] = icand;
- }
- }
- Melder_assert (optimalCandidates [itab] != 0);
- }
- /*
- Create linear programming problem.
- */
- linprog = NUMlinprog_new (false);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- NUMlinprog_addVariable (linprog, weightFloor, undefined, 1.0);
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- integer ioptimalCandidate = optimalCandidates [itab];
- Melder_assert (ioptimalCandidate >= 1);
- Melder_assert (ioptimalCandidate <= tab -> numberOfCandidates);
- OTGrammarCandidate optimalCandidate = & tab -> candidates [ioptimalCandidate];
- for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) if (icand != ioptimalCandidate) {
- OTGrammarCandidate cand = & tab -> candidates [icand];
- NUMlinprog_addConstraint (linprog, marginOfSeparation, undefined);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
- NUMlinprog_addConstraintCoefficient (linprog, cand -> marks [icons] - optimalCandidate -> marks [icons]);
- }
- }
- NUMlinprog_run (linprog);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- double weighting = NUMlinprog_getPrimalValue (linprog, icons);
- Melder_assert (weighting >= weightFloor);
- my constraints [icons]. ranking = my constraints [icons]. disharmony =
- my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ? log (weighting) : weighting;
- }
- NUMlinprog_delete (linprog);
- return result;
- } catch (MelderError) {
- NUMlinprog_delete (linprog);
- Melder_throw (me, U": positive weights not found.");
- }
- }
- void OTGrammar_reset (OTGrammar me, double ranking) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- constraint -> disharmony = constraint -> ranking = ranking;
- }
- OTGrammar_sort (me);
- }
- void OTGrammar_resetToRandomRanking (OTGrammar me, double mean, double standardDeviation) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- constraint -> disharmony = constraint -> ranking = NUMrandomGauss (mean, standardDeviation);
- }
- OTGrammar_sort (me);
- }
- void OTGrammar_resetToRandomTotalRanking (OTGrammar me, double maximumRanking, double rankingDistance) {
- /*
- First put the constraints in a random order and build a random index.
- */
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- constraint -> ranking = 0.0;
- }
- OTGrammar_newDisharmonies (me, 1.0);
- /*
- Then use the random index to yield a cascade of rankings.
- */
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- constraint -> disharmony = constraint -> ranking = maximumRanking - (icons - 1) * rankingDistance;
- }
- OTGrammar_sort (me);
- }
- void OTGrammar_setRanking (OTGrammar me, integer constraint, double ranking, double disharmony) {
- try {
- Melder_require (constraint > 0 && constraint <= my numberOfConstraints,
- U"There is no constraint with number ", constraint, U".");
- my constraints [constraint]. ranking = ranking;
- my constraints [constraint]. disharmony = disharmony;
- OTGrammar_sort (me);
- } catch (MelderError) {
- Melder_throw (me, U": ranking of constraint ", constraint, U" not set.");
- }
- }
- void OTGrammar_setConstraintPlasticity (OTGrammar me, integer constraint, double plasticity) {
- try {
- Melder_require (constraint > 0 && constraint <= my numberOfConstraints,
- U"There is no constraint with number ", constraint, U".");
- my constraints [constraint]. plasticity = plasticity;
- } catch (MelderError) {
- Melder_throw (me, U": plasticity of constraint ", constraint, U" not set.");
- }
- }
- integer theSaveNumberOfConstraints, *theSaveIndex;
- double *theSaveRankings, *theSaveDisharmonies;
- bool *theSaveTiedToTheLeft, *theSaveTiedToTheRight;
- static void OTGrammar_save (OTGrammar me) {
- if (my numberOfConstraints != theSaveNumberOfConstraints) {
- NUMvector_free (theSaveIndex, 1); theSaveIndex = nullptr;
- NUMvector_free (theSaveRankings, 1); theSaveRankings = nullptr;
- NUMvector_free (theSaveDisharmonies, 1); theSaveDisharmonies = nullptr;
- NUMvector_free (theSaveTiedToTheLeft, 1); theSaveTiedToTheLeft = nullptr;
- NUMvector_free (theSaveTiedToTheRight, 1); theSaveTiedToTheRight = nullptr;
- theSaveNumberOfConstraints = my numberOfConstraints;
- }
- if (! theSaveIndex) theSaveIndex = NUMvector <integer> (1, my numberOfConstraints);
- if (! theSaveRankings) theSaveRankings = NUMvector <double> (1, my numberOfConstraints);
- if (! theSaveDisharmonies) theSaveDisharmonies = NUMvector <double> (1, my numberOfConstraints);
- if (! theSaveTiedToTheLeft) theSaveTiedToTheLeft = NUMvector <bool> (1, my numberOfConstraints);
- if (! theSaveTiedToTheRight) theSaveTiedToTheRight = NUMvector <bool> (1, my numberOfConstraints);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- theSaveIndex [icons] = my index [icons];
- theSaveRankings [icons] = my constraints [icons]. ranking;
- theSaveDisharmonies [icons] = my constraints [icons]. disharmony;
- theSaveTiedToTheLeft [icons] = my constraints [icons]. tiedToTheLeft;
- theSaveTiedToTheRight [icons] = my constraints [icons]. tiedToTheRight;
- }
- }
- static void OTGrammar_restore (OTGrammar me) {
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- my index [icons] = theSaveIndex [icons];
- my constraints [icons]. ranking = theSaveRankings [icons];
- my constraints [icons]. disharmony = theSaveDisharmonies [icons];
- my constraints [icons]. tiedToTheLeft = theSaveTiedToTheLeft [icons];
- my constraints [icons]. tiedToTheRight = theSaveTiedToTheRight [icons];
- }
- }
- void OTGrammar_learnOneFromPartialOutput (OTGrammar me, conststring32 partialAdultOutput,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, integer numberOfChews, bool warnIfStalled)
- {
- try {
- OTGrammar_newDisharmonies (me, evaluationNoise);
- if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD)
- OTGrammar_save (me);
- integer ichew = 1;
- for (; ichew <= numberOfChews; ichew ++) {
- integer assumedAdultInputTableau, assumedAdultCandidate;
- OTGrammar_getInterpretiveParse (me, partialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
- bool grammarHasChanged = false;
- OTGrammar_learnOne (me,
- my tableaus [assumedAdultInputTableau]. input.get(),
- my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get(),
- evaluationNoise, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, Melder_debug == 47, warnIfStalled, & grammarHasChanged
- );
- if (! grammarHasChanged)
- return;
- }
- if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD && ichew > numberOfChews) {
- /*
- Is the partial output form grammatical by now?
- */
- integer assumedAdultInputTableau, assumedAdultCandidate;
- OTGrammar_getInterpretiveParse (me, partialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
- OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [OTGrammar_getWinner (me, assumedAdultInputTableau)];
- if (! str32equ (learnerCandidate -> output.get(),
- my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
- { /* Still ungrammatical? */
- /*
- Backtrack as in Tesar & Smolensky 2000:69.
- */
- OTGrammar_restore (me);
- }
- }
- } catch (MelderError) {
- Melder_throw (me, U": not learned from partial adult output \"", partialAdultOutput, U"\".");
- }
- }
- static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, conststring32 partialAdultOutput, integer ipartialAdultOutput,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, integer numberOfChews, bool warnIfStalled,
- bool resampleForVirtualProduction, bool compareOnlyPartialOutput, integer resampleForCorrectForm)
- {
- try {
- OTGrammar_newDisharmonies (me, evaluationNoise);
- if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD)
- OTGrammar_save (me);
- integer ichew = 1;
- for (; ichew <= numberOfChews; ichew ++) {
- integer assumedAdultInputTableau, assumedAdultCandidate;
- OTGrammar_getInterpretiveParse_opt (me, ipartialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
- OTGrammarTableau tableau = & my tableaus [assumedAdultInputTableau];
- OTGrammarCandidate assumedCorrect = & tableau -> candidates [assumedAdultCandidate];
- /*
- Determine the "winner", i.e. the candidate that wins in the learner's grammar
- (Tesar & Smolensky call this the "loser").
- */
- if (resampleForVirtualProduction) OTGrammar_newDisharmonies (me, evaluationNoise);
- integer iwinner = OTGrammar_getWinner (me, assumedAdultInputTableau);
- OTGrammarCandidate winner = & tableau -> candidates [iwinner];
- /*
- Error-driven: compare the adult winner (the correct candidate) and the learner's winner.
- */
- if (compareOnlyPartialOutput) {
- if (str32str (winner -> output.get(), partialAdultOutput)) return; // as far as we know, the grammar is already correct: don't update rankings
- } else {
- if (str32equ (winner -> output.get(), assumedCorrect -> output.get())) return; // as far as we know, the grammar is already correct: don't update rankings
- }
- if (resampleForCorrectForm) {
- integer itry = 1;
- for (; itry <= resampleForCorrectForm; itry ++) {
- OTGrammar_newDisharmonies (me, evaluationNoise);
- integer iwinner2 = OTGrammar_getWinner (me, assumedAdultInputTableau);
- OTGrammarCandidate winner2 = & tableau -> candidates [iwinner2];
- if (compareOnlyPartialOutput) {
- if (str32str (winner2 -> output.get(), partialAdultOutput)) { assumedAdultCandidate = iwinner2; break; }
- } else {
- if (str32equ (winner2 -> output.get(), assumedCorrect -> output.get())) { assumedAdultCandidate = iwinner2; break; }
- }
- }
- if (itry > resampleForCorrectForm) return; // no match, so bail out
- }
- /*
- Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
- The grammar will have to change.
- */
- bool grammarHasChanged = false;
- OTGrammar_modifyRankings (me, assumedAdultInputTableau, iwinner, assumedAdultCandidate, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, warnIfStalled, & grammarHasChanged);
- if (! grammarHasChanged)
- return;
- }
- if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD && ichew > numberOfChews) {
- /*
- Is the partial output form grammatical by now?
- */
- integer assumedAdultInputTableau, assumedAdultCandidate;
- OTGrammar_getInterpretiveParse_opt (me, ipartialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
- OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [OTGrammar_getWinner (me, assumedAdultInputTableau)];
- if (! str32equ (learnerCandidate -> output.get(),
- my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
- { /* Still ungrammatical? */
- /*
- Backtrack as in Tesar & Smolensky 2000:69.
- */
- OTGrammar_restore (me);
- }
- }
- } catch (MelderError) {
- Melder_throw (me, U": not learned from partial adult output ", partialAdultOutput, U".");
- }
- }
- static autoOTHistory OTGrammar_createHistory (OTGrammar me, integer storeHistoryEvery, integer numberOfData) {
- try {
- integer numberOfSamplingPoints = numberOfData / storeHistoryEvery; // e.g. 0, 20, 40, ...
- autoOTHistory thee = Thing_new (OTHistory);
- TableOfReal_init (thee.get(), 2 + numberOfSamplingPoints * 2, 1 + my numberOfConstraints);
- TableOfReal_setColumnLabel (thee.get(), 1, U"Datum");
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- TableOfReal_setColumnLabel (thee.get(), icons + 1, my constraints [icons]. name.get());
- }
- TableOfReal_setRowLabel (thee.get(), 1, U"Initial state");
- thy data [1] [1] = 0.0;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- thy data [1] [icons + 1] = my constraints [icons]. ranking;
- }
- return thee;
- } catch (MelderError) {
- Melder_throw (me, U": history not created.");
- }
- }
- static void OTGrammar_updateHistory (OTGrammar me, OTHistory thee, integer storeHistoryEvery, integer datumNumber, conststring32 input) {
- try {
- if (datumNumber % storeHistoryEvery == 0) {
- integer rowNumber = 2 * datumNumber / storeHistoryEvery;
- TableOfReal_setRowLabel (thee, rowNumber, input);
- thy data [rowNumber] [1] = datumNumber;
- thy data [rowNumber + 1] [1] = datumNumber;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- thy data [rowNumber] [icons + 1] = my constraints [icons]. disharmony;
- thy data [rowNumber + 1] [icons + 1] = my constraints [icons]. ranking;
- }
- }
- } catch (MelderError) {
- Melder_throw (me, U": history not updated.");
- }
- }
- static void OTGrammar_finalizeHistory (OTGrammar me, OTHistory thee, integer datumNumber) {
- try {
- TableOfReal_setRowLabel (thee, thy numberOfRows, U"Final state");
- thy data [thy numberOfRows] [1] = datumNumber;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- thy data [thy numberOfRows] [icons + 1] = my constraints [icons]. ranking;
- }
- } catch (MelderError) {
- Melder_throw (me, U": history not finalized.");
- }
- }
- void OTGrammar_learnFromPartialOutputs (OTGrammar me, Strings partialOutputs,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double plasticity, double relativePlasticityNoise, integer numberOfChews,
- integer storeHistoryEvery, autoOTHistory *history_out)
- {
- try {
- autoOTHistory history;
- if (storeHistoryEvery) {
- history = OTGrammar_createHistory (me, storeHistoryEvery, partialOutputs -> numberOfStrings);
- }
- try {
- for (integer idatum = 1; idatum <= partialOutputs -> numberOfStrings; idatum ++) {
- try {
- OTGrammar_learnOneFromPartialOutput (me, partialOutputs -> strings [idatum].get(),
- evaluationNoise, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, numberOfChews, false);
- } catch (MelderError) {
- if (history) {
- OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, partialOutputs -> strings [idatum].get()); // so that we can inspect
- }
- throw;
- }
- if (history) {
- OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, partialOutputs -> strings [idatum].get());
- }
- }
- if (history) {
- OTGrammar_finalizeHistory (me, history.get(), partialOutputs -> numberOfStrings);
- }
- *history_out = history.move();
- } catch (MelderError) {
- *history_out = history.move(); // so that we can inspect
- throw;
- }
- } catch (MelderError) {
- Melder_throw (me, U": not learned from partial outputs ", partialOutputs, U".");
- }
- }
- static void OTGrammar_opt_deleteOutputMatching (OTGrammar me) {
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tab -> candidates [icand];
- cand -> numberOfPotentialPartialOutputsMatching = 0;
- NUMvector_free (cand -> partialOutputMatches, 1);
- cand -> partialOutputMatches = nullptr;
- }
- }
- }
- static void OTGrammar_Distributions_opt_createOutputMatching (OTGrammar me, Distributions thee, integer columnNumber) {
- try {
- if (columnNumber > thy numberOfColumns)
- Melder_throw (U"No column ", columnNumber, U" in Distributions.");
- if (thy numberOfRows < 1)
- Melder_throw (U"No candidates in Distributions.");
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tab -> candidates [icand];
- cand -> numberOfPotentialPartialOutputsMatching = thy numberOfRows;
- cand -> partialOutputMatches = NUMvector <bool> (1, thy numberOfRows);
- }
- }
- for (integer ipartialOutput = 1; ipartialOutput <= thy numberOfRows; ipartialOutput ++) {
- if (thy data [ipartialOutput] [columnNumber] > 0.0) {
- conststring32 partialOutput = thy rowLabels [ipartialOutput].get();
- bool foundPartialOutput = false;
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
- OTGrammarCandidate cand = & tab -> candidates [icand];
- if (str32str (cand -> output.get(), partialOutput)) {
- foundPartialOutput = true;
- cand -> partialOutputMatches [ipartialOutput] = true;
- }
- }
- }
- if (! foundPartialOutput)
- Melder_throw (U"The partial output \"", partialOutput, U"\" does not match any candidate for any input form.");
- }
- }
- } catch (MelderError) {
- OTGrammar_opt_deleteOutputMatching (me);
- throw;
- }
- }
- void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distributions thee, integer columnNumber,
- double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
- double initialPlasticity, integer replicationsPerPlasticity, double plasticityDecrement,
- integer numberOfPlasticities, double relativePlasticityNoise, integer numberOfChews,
- integer storeHistoryEvery, autoOTHistory *history_out,
- bool resampleForVirtualProduction, bool compareOnlyPartialOutput, integer resampleForCorrectForm)
- {
- integer idatum = 0;
- const integer numberOfData = numberOfPlasticities * replicationsPerPlasticity;
- try {
- autoOTHistory history;
- OTGrammar_Distributions_opt_createOutputMatching (me, thee, columnNumber);
- autoMelderMonitor monitor (U"Learning with limited knowledge...");
- if (monitor.graphics())
- Graphics_clearWs (monitor.graphics());
- if (storeHistoryEvery)
- history = OTGrammar_createHistory (me, storeHistoryEvery, numberOfData);
- try {
- double plasticity = initialPlasticity;
- for (integer iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
- for (integer ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
- conststring32 partialOutput;
- integer ipartialOutput;
- Distributions_peek (thee, columnNumber, & partialOutput, & ipartialOutput);
- ++ idatum;
- if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
- Graphics_beginMovieFrame (monitor.graphics(), nullptr);
- Graphics_setWindow (monitor.graphics(), 0, numberOfData, 50, 150);
- for (integer icons = 1; icons <= 14 && icons <= my numberOfConstraints; icons ++) {
- Graphics_setGrey (monitor.graphics(), (double) icons / 14);
- Graphics_line (monitor.graphics(), idatum, my constraints [icons]. ranking,
- idatum, my constraints [icons]. ranking+1);
- }
- Graphics_endMovieFrame (monitor.graphics(), 0.0);
- }
- Melder_monitor ((double) idatum / numberOfData,
- U"Processing partial output ", idatum, U" out of ", numberOfData, U": ",
- thy rowLabels [ipartialOutput].get());
- try {
- OTGrammar_learnOneFromPartialOutput_opt (me, partialOutput, ipartialOutput,
- evaluationNoise, updateRule, honourLocalRankings,
- plasticity, relativePlasticityNoise, numberOfChews, false,
- resampleForVirtualProduction, compareOnlyPartialOutput, resampleForCorrectForm); // no warning if stalled: RIP form is allowed to be harmonically bounded
- } catch (MelderError) {
- if (history)
- OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, thy rowLabels [ipartialOutput].get());
- throw;
- }
- if (history)
- OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, thy rowLabels [ipartialOutput].get());
- }
- plasticity *= plasticityDecrement;
- }
- if (history) {
- OTGrammar_finalizeHistory (me, history.get(), numberOfData);
- }
- OTGrammar_opt_deleteOutputMatching (me);
- if (history_out)
- *history_out = history.move();
- } catch (MelderError) {
- OTGrammar_opt_deleteOutputMatching (me);
- if (history_out)
- *history_out = history.move(); // so that we can inspect
- throw;
- }
- } catch (MelderError) {
- if (idatum > 1)
- Melder_appendError (U"Only ", idatum - 1, U" input-output pairs out of ", numberOfData, U" were processed.");
- Melder_throw (me, U" & ", thee, U": not learned from partial outputs.");
- }
- }
- double OTGrammar_PairDistribution_getFractionCorrect (OTGrammar me, PairDistribution thee,
- double evaluationNoise, integer numberOfInputs)
- {
- try {
- integer numberOfCorrect = 0;
- for (integer ireplication = 1; ireplication <= numberOfInputs; ireplication ++) {
- conststring32 input, adultOutput;
- PairDistribution_peekPair (thee, & input, & adultOutput);
- OTGrammar_newDisharmonies (me, evaluationNoise);
- integer inputTableau = OTGrammar_getTableau (me, input);
- OTGrammarCandidate learnerCandidate = & my tableaus [inputTableau]. candidates [OTGrammar_getWinner (me, inputTableau)];
- if (str32equ (learnerCandidate -> output.get(), adultOutput))
- numberOfCorrect ++;
- }
- return (double) numberOfCorrect / numberOfInputs;
- } catch (MelderError) {
- Melder_throw (me, U" & ", thee, U": fraction correct not computed.");
- }
- }
- integer OTGrammar_PairDistribution_getMinimumNumberCorrect (OTGrammar me, PairDistribution thee,
- double evaluationNoise, integer numberOfReplications)
- {
- try {
- integer minimumNumberCorrect = numberOfReplications;
- for (integer ipair = 1; ipair <= thy pairs.size; ipair ++) {
- PairProbability prob = thy pairs.at [ipair];
- if (prob -> weight > 0.0) {
- integer numberOfCorrect = 0;
- conststring32 input = prob -> string1.get(), adultOutput = prob -> string2.get();
- integer inputTableau = OTGrammar_getTableau (me, input);
- for (integer ireplication = 1; ireplication <= numberOfReplications; ireplication ++) {
- OTGrammar_newDisharmonies (me, evaluationNoise);
- OTGrammarCandidate learnerCandidate = & my tableaus [inputTableau]. candidates [OTGrammar_getWinner (me, inputTableau)];
- if (str32equ (learnerCandidate -> output.get(), adultOutput))
- numberOfCorrect ++;
- }
- if (numberOfCorrect < minimumNumberCorrect)
- minimumNumberCorrect = numberOfCorrect;
- }
- }
- return minimumNumberCorrect;
- } catch (MelderError) {
- Melder_throw (me, U" & ", thee, U": minimum number correct not computed.");
- }
- }
- double OTGrammar_Distributions_getFractionCorrect (OTGrammar me, Distributions thee, integer columnNumber,
- double evaluationNoise, integer numberOfInputs)
- {
- try {
- integer numberOfCorrect = 0;
- OTGrammar_Distributions_opt_createOutputMatching (me, thee, columnNumber);
- for (integer ireplication = 1; ireplication <= numberOfInputs; ireplication ++) {
- integer ipartialOutput;
- Distributions_peek (thee, columnNumber, nullptr, & ipartialOutput);
- OTGrammar_newDisharmonies (me, evaluationNoise);
- integer assumedAdultInputTableau, assumedAdultCandidate;
- OTGrammar_getInterpretiveParse_opt (me, ipartialOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
- OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [OTGrammar_getWinner (me, assumedAdultInputTableau)];
- if (str32equ (learnerCandidate -> output.get(), my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
- numberOfCorrect ++;
- }
- OTGrammar_opt_deleteOutputMatching (me);
- return (double) numberOfCorrect / numberOfInputs;
- } catch (MelderError) {
- Melder_throw (me, U" & ", thee, U": fraction correct not computed.");
- }
- }
- void OTGrammar_removeConstraint (OTGrammar me, conststring32 constraintName) {
- try {
- integer removed = 0;
- Melder_require (my numberOfConstraints > 1,
- U"Cannot remove last remaining constraint.");
- /*
- Look for the constraint to be removed.
- */
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [icons];
- if (str32equ (constraint -> name.get(), constraintName)) {
- removed = icons;
- break;
- }
- }
- if (removed == 0)
- Melder_throw (U"No such constraint.");
- /*
- Remove the constraint while reusing the memory space.
- */
- for (integer icons = removed; icons < my numberOfConstraints; icons ++) {
- my constraints [icons] = std::move (my constraints [icons + 1]);
- }
- my constraints [my numberOfConstraints]. destroy (); // this will do nothing except if the removed constraint is the last one
- my numberOfConstraints -= 1;
- /*
- Remove or shift fixed rankings.
- */
- for (integer ifixed = my numberOfFixedRankings; ifixed > 0; ifixed --) {
- OTGrammarFixedRanking fixed = & my fixedRankings [ifixed];
- if (fixed -> higher == removed || fixed -> lower == removed) {
- /*
- Remove fixed ranking.
- */
- my numberOfFixedRankings -= 1;
- if (my numberOfFixedRankings == 0) {
- NUMvector_free <structOTGrammarFixedRanking> (my fixedRankings, 1);
- my fixedRankings = nullptr;
- }
- for (integer jfixed = ifixed; jfixed <= my numberOfFixedRankings; jfixed ++)
- my fixedRankings [jfixed] = my fixedRankings [jfixed + 1];
- } else {
- /*
- Shift fixed ranking.
- */
- if (fixed -> higher > removed)
- fixed -> higher -= 1;
- if (fixed -> lower > removed)
- fixed -> lower -= 1;
- }
- }
- /*
- Shift tableau rows.
- */
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- candidate -> numberOfConstraints -= 1;
- for (integer icons = removed; icons <= my numberOfConstraints; icons ++)
- candidate -> marks [icons] = candidate -> marks [icons + 1];
- }
- }
- /*
- Rebuild index.
- */
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) my index [icons] = icons;
- OTGrammar_sort (me);
- } catch (MelderError) {
- Melder_throw (me, U": constraint \"", constraintName, U"\" not removed.");
- }
- }
- static void OTGrammarTableau_removeCandidate_unstripped (OTGrammarTableau me, integer candidateNumber) {
- Melder_assert (candidateNumber >= 1);
- if (candidateNumber > my numberOfCandidates)
- Melder_fatal (U"icand ", candidateNumber, U", ncand ", my numberOfCandidates);
- for (integer jcand = candidateNumber; jcand < my numberOfCandidates; jcand ++) {
- OTGrammarCandidate candj = & my candidates [jcand];
- OTGrammarCandidate candj1 = & my candidates [jcand + 1];
- candj -> output = candj1 -> output. move();
- candj -> marks = candj1 -> marks. move();
- }
- my candidates [my numberOfCandidates]. output. reset();
- my candidates [my numberOfCandidates]. marks. reset();
- my numberOfCandidates -= 1;
- }
- static bool OTGrammarTableau_isHarmonicallyBounded (OTGrammarTableau me, integer icand, integer jcand) {
- OTGrammarCandidate candi = & my candidates [icand], candj = & my candidates [jcand];
- bool equal = true;
- if (icand == jcand)
- return false;
- for (integer icons = 1; icons <= candi -> numberOfConstraints; icons ++) {
- if (candi -> marks [icons] < candj -> marks [icons])
- return false;
- if (candi -> marks [icons] > candj -> marks [icons])
- equal = false;
- }
- return ! equal;
- }
- static bool OTGrammarTableau_candidateIsPossibleWinner (OTGrammar me, integer itab, integer icand) {
- OTGrammar_save (me);
- OTGrammar_reset (me, 100.0);
- for (;;) {
- bool grammarHasChanged = false;
- OTGrammar_learnOne (me, my tableaus [itab]. input.get(), my tableaus [itab]. candidates [icand]. output.get(),
- 1e-3, kOTGrammar_rerankingStrategy::EDCD, false, 1.0, 0.0, true, true, & grammarHasChanged);
- if (! grammarHasChanged) {
- OTGrammar_restore (me);
- return true;
- }
- double previousStratum = 101.0;
- OTGrammar_newDisharmonies (me, 0.0);
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- double stratum = my constraints [my index [icons]]. ranking;
- #if 0
- if (stratum < 50.0 - my numberOfConstraints) {
- OTGrammar_restore (me);
- return false; // we detected a tumble
- }
- #else
- if (stratum < previousStratum) {
- if (stratum < previousStratum - 1.0) {
- OTGrammar_restore (me);
- return false; // we detected a vacated stratum
- }
- previousStratum = stratum;
- }
- #endif
- }
- }
- return false; // cannot occur
- }
- void OTGrammar_removeHarmonicallyBoundedCandidates (OTGrammar me, bool singly) {
- try {
- /*
- First, the candidates that are harmonically bounded by one or more single other candidates have to be removed;
- otherwise, EDCD will stall.
- */
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- for (integer icand = tab -> numberOfCandidates; icand >= 1; icand --) {
- for (integer jcand = 1; jcand <= tab -> numberOfCandidates; jcand ++) {
- if (OTGrammarTableau_isHarmonicallyBounded (tab, icand, jcand)) {
- OTGrammarTableau_removeCandidate_unstripped (tab, icand);
- break;
- }
- }
- }
- tab -> candidates = (OTGrammarCandidate) realloc (& tab -> candidates [1], sizeof (struct structOTGrammarCandidate) * tab -> numberOfCandidates) - 1;
- }
- if (! singly) {
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tab = & my tableaus [itab];
- for (integer icand = tab -> numberOfCandidates; icand >= 1; icand --) {
- if (! OTGrammarTableau_candidateIsPossibleWinner (me, itab, icand))
- OTGrammarTableau_removeCandidate_unstripped (tab, icand);
- }
- tab -> candidates = (OTGrammarCandidate) realloc (& tab -> candidates [1], sizeof (struct structOTGrammarCandidate) * tab -> numberOfCandidates) - 1;
- }
- }
- } catch (MelderError) {
- Melder_throw (me, U": not all harmonically bounded candidates were removed.");
- }
- }
- Thing_define (OTGrammar_List4, Daata) {
- // new data:
- integer hi1, lo1, hi2, lo2;
- };
- Thing_implement (OTGrammar_List4, Daata, 0);
- void OTGrammar_PairDistribution_listObligatoryRankings (OTGrammar me, PairDistribution thee) {
- /*
- Save.
- */
- OTGrammarFixedRanking savedFixedRankings = my fixedRankings; // dangle...
- my fixedRankings = nullptr; // ...undangle
- integer savedNumberOfFixedRankings = my numberOfFixedRankings;
- OTGrammar_save (me);
- try {
- integer ipair = 0, npair = my numberOfConstraints * (my numberOfConstraints - 1);
- integer itrial;
- const double evaluationNoise = 1e-9;
- /*
- Add room for two more fixed rankings.
- */
- my fixedRankings = NUMvector <structOTGrammarFixedRanking> (1, my numberOfFixedRankings + 2);
- for (integer ifixedRanking = 1; ifixedRanking <= my numberOfFixedRankings; ifixedRanking ++) {
- my fixedRankings [ifixedRanking]. higher = savedFixedRankings [ifixedRanking]. higher;
- my fixedRankings [ifixedRanking]. lower = savedFixedRankings [ifixedRanking]. lower;
- }
- /*
- Test whether there are rankings at all for these output data.
- */
- OTGrammar_reset (me, 100.0);
- for (itrial = 1; itrial <= 40; itrial ++) {
- bool grammarHasChangedDuringCycle = false;
- OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
- OTGrammar_newDisharmonies (me, evaluationNoise);
- for (integer iform = 1; iform <= thy pairs.size; iform ++) {
- PairProbability prob = thy pairs.at [iform];
- if (prob -> weight > 0.0) {
- bool grammarHasChanged = false;
- OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
- evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
- 1.0, 0.0, false, true, & grammarHasChanged
- );
- if (grammarHasChanged)
- OTGrammar_newDisharmonies (me, evaluationNoise);
- grammarHasChangedDuringCycle |= grammarHasChanged;
- }
- }
- if (! grammarHasChangedDuringCycle) break;
- }
- if (itrial > 40) {
- MelderInfo_writeLine (U"There are no total rankings that generate these input-output pairs.");
- throw MelderError ();
- }
- /*
- Test learnability of every possible ranked pair.
- */
- my numberOfFixedRankings ++;
- autoNUMmatrix <bool> obligatory (1, my numberOfConstraints, 1, my numberOfConstraints);
- MelderInfo_open ();
- autoMelderProgress progress (U"Finding obligatory rankings.");
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons) {
- my fixedRankings [my numberOfFixedRankings]. higher = icons;
- my fixedRankings [my numberOfFixedRankings]. lower = jcons;
- OTGrammar_reset (me, 100.0);
- Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair, U": Trying ranking ",
- my constraints [icons]. name.get(), U" >> ", my constraints [jcons]. name.get());
- ipair ++;
- for (itrial = 1; itrial <= 40; itrial ++) {
- bool grammarHasChangedDuringCycle = false;
- OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
- OTGrammar_newDisharmonies (me, evaluationNoise);
- for (integer iform = 1; iform <= thy pairs.size; iform ++) {
- PairProbability prob = thy pairs.at [iform];
- if (prob -> weight > 0.0) {
- bool grammarHasChanged = false;
- OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
- evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
- 1.0, 0.0, false, true, & grammarHasChanged);
- if (grammarHasChanged)
- OTGrammar_newDisharmonies (me, evaluationNoise);
- grammarHasChangedDuringCycle |= grammarHasChanged;
- }
- }
- if (! grammarHasChangedDuringCycle)
- break;
- }
- if (itrial > 40) {
- obligatory [jcons] [icons] = true;
- MelderInfo_writeLine (my constraints [jcons]. name.get(), U" >> ", my constraints [icons]. name.get());
- MelderInfo_close ();
- }
- }
- }
- my numberOfFixedRankings ++;
- Melder_progress (0.0, U"");
- npair = npair * npair;
- OrderedOf<structOTGrammar_List4> list;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons && ! obligatory [jcons] [icons]) {
- my fixedRankings [my numberOfFixedRankings - 1]. higher = icons;
- my fixedRankings [my numberOfFixedRankings - 1]. lower = jcons;
- for (integer kcons = icons; kcons <= my numberOfConstraints; kcons ++) {
- for (integer lcons = 1; lcons <= my numberOfConstraints; lcons ++) if (kcons != lcons && ! obligatory [lcons] [kcons]) {
- if (icons == kcons && jcons >= lcons) continue;
- if (icons == lcons && jcons == kcons) continue;
- if (jcons == kcons && obligatory [lcons] [icons]) continue;
- if (icons == lcons && obligatory [jcons] [kcons]) continue;
- if (obligatory [lcons] [icons] && obligatory [jcons] [kcons]) continue;
- my fixedRankings [my numberOfFixedRankings]. higher = kcons;
- my fixedRankings [my numberOfFixedRankings]. lower = lcons;
- OTGrammar_reset (me, 100.0);
- Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair);
- ipair ++;
- for (itrial = 1; itrial <= 40; itrial ++) {
- bool grammarHasChangedDuringCycle = false;
- OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
- OTGrammar_newDisharmonies (me, evaluationNoise);
- for (integer iform = 1; iform <= thy pairs.size; iform ++) {
- PairProbability prob = thy pairs.at [iform];
- if (prob -> weight > 0.0) {
- bool grammarHasChanged = false;
- OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
- evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
- 1.0, 0.0, false, true, & grammarHasChanged);
- if (grammarHasChanged)
- OTGrammar_newDisharmonies (me, evaluationNoise);
- grammarHasChangedDuringCycle |= grammarHasChanged;
- }
- }
- if (! grammarHasChangedDuringCycle) break;
- }
- if (itrial > 40) {
- autoOTGrammar_List4 listElement = Thing_new (OTGrammar_List4);
- listElement -> hi1 = jcons;
- listElement -> lo1 = icons;
- listElement -> hi2 = lcons;
- listElement -> lo2 = kcons;
- list. addItem_move (listElement.move());
- }
- }
- }
- }
- }
- Melder_progress (1.0);
- /*
- Improve list.
- */
- bool improved = true;
- while (improved) {
- improved = false;
- for (integer ilist = 1; ilist <= list.size; ilist ++) {
- for (integer jlist = 1; jlist <= list.size; jlist ++) if (ilist != jlist) {
- OTGrammar_List4 elA = list.at [ilist];
- OTGrammar_List4 elB = list.at [jlist];
- integer ahi1 = elA -> hi1, alo1 = elA -> lo1, ahi2 = elA -> hi2, alo2 = elA -> lo2;
- integer bhi1 = elB -> hi1, blo1 = elB -> lo1, bhi2 = elB -> hi2, blo2 = elB -> lo2;
- improved |= (ahi1 == bhi1 || obligatory [bhi1] [ahi1]) && (ahi2 == bhi2 || obligatory [bhi2] [ahi2]) &&
- (alo1 == blo1 || obligatory [alo1] [blo1]) && (alo2 == blo2 || obligatory [alo2] [blo2]);
- improved |= (ahi1 == bhi2 || obligatory [bhi2] [ahi1]) && (ahi2 == bhi1 || obligatory [bhi1] [ahi2]) &&
- (alo1 == blo2 || obligatory [alo1] [blo2]) && (alo2 == blo1 || obligatory [alo2] [blo1]);
- if (improved) {
- list. removeItem (jlist);
- break;
- }
- }
- if (improved) break;
- }
- }
- improved = true;
- while (improved) {
- improved = false;
- for (integer ilist = 1; ilist <= list.size; ilist ++) {
- for (integer jlist = 1; jlist <= list.size; jlist ++) if (ilist != jlist) {
- OTGrammar_List4 elA = list.at [ilist];
- OTGrammar_List4 elB = list.at [jlist];
- integer ahi1 = elA -> hi1, alo1 = elA -> lo1, ahi2 = elA -> hi2, alo2 = elA -> lo2;
- integer bhi1 = elB -> hi1, blo1 = elB -> lo1, bhi2 = elB -> hi2, blo2 = elB -> lo2;
- improved |= ahi1 == bhi1 && alo1 == blo1 && ahi2 == bhi2 && blo2 == bhi1 && alo2 == alo1;
- improved |= ahi1 == bhi2 && alo1 == blo2 && ahi2 == bhi1 && blo1 == bhi2 && alo2 == alo1;
- improved |= ahi2 == bhi1 && alo2 == blo1 && ahi1 == bhi2 && blo2 == bhi1 && alo1 == alo2;
- improved |= ahi2 == bhi2 && alo2 == blo2 && ahi1 == bhi1 && blo1 == bhi2 && alo1 == alo2;
- if (improved) {
- list. removeItem (jlist);
- break;
- }
- }
- if (improved) break;
- }
- }
- for (integer ilist = 1; ilist <= list.size; ilist ++) {
- OTGrammar_List4 el = list.at [ilist];
- MelderInfo_write (my constraints [el -> hi1]. name.get(), U" >> ", my constraints [el -> lo1]. name.get(), U" OR ");
- MelderInfo_writeLine (my constraints [el -> hi2]. name.get(), U" >> ", my constraints [el -> lo2]. name.get());
- MelderInfo_close ();
- }
- MelderInfo_close ();
- /*
- Remove room.
- */
- NUMvector_free <structOTGrammarFixedRanking> (my fixedRankings, 1); // dangle
- /*
- Restore.
- */
- my numberOfFixedRankings = savedNumberOfFixedRankings;
- my fixedRankings = savedFixedRankings; // undangle
- OTGrammar_restore (me);
- } catch (MelderError) {
- MelderInfo_close ();
- /*
- Remove room.
- */
- NUMvector_free <structOTGrammarFixedRanking> (my fixedRankings, 1); // dangle
- /*
- Restore.
- */
- my numberOfFixedRankings = savedNumberOfFixedRankings;
- my fixedRankings = savedFixedRankings; // undangle
- OTGrammar_restore (me);
- Melder_throw (me, U": obligatory rankings not listed.");
- }
- }
- void OTGrammar_Distributions_listObligatoryRankings (OTGrammar me, Distributions thee, integer columnNumber) {
- /*
- Save.
- */
- OTGrammarFixedRanking savedFixedRankings = my fixedRankings;
- my fixedRankings = nullptr;
- OTGrammar_save (me);
- try {
- integer ipair = 0, npair = my numberOfConstraints * (my numberOfConstraints - 1);
- /*
- Add room for one more fixed ranking.
- */
- my numberOfFixedRankings ++;
- my fixedRankings = NUMvector <structOTGrammarFixedRanking> (1, my numberOfFixedRankings);
- for (integer ifixedRanking = 1; ifixedRanking < my numberOfFixedRankings; ifixedRanking ++) {
- my fixedRankings [ifixedRanking]. higher = savedFixedRankings [ifixedRanking]. higher;
- my fixedRankings [ifixedRanking]. lower = savedFixedRankings [ifixedRanking]. lower;
- }
- /*
- Test learnability of every possible ranked pair.
- */
- MelderInfo_open ();
- autoMelderProgress progress (U"Finding obligatory rankings.");
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons) {
- my fixedRankings [my numberOfFixedRankings]. higher = icons;
- my fixedRankings [my numberOfFixedRankings]. lower = jcons;
- OTGrammar_reset (me, 100.0);
- Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair, U": Trying ranking ",
- my constraints [icons]. name.get(), U" >> ", my constraints [jcons]. name.get());
- ipair ++;
- Melder_progressOff ();
- OTGrammar_Distributions_learnFromPartialOutputs (me, thee, columnNumber,
- 1e-9, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
- 1.0, 1000, 0.0, 1, 0.0, 1, 0, nullptr, false, false, 0);
- Melder_progressOn ();
- for (integer kcons = 1; kcons <= my numberOfConstraints; kcons ++) {
- if (my constraints [kcons]. ranking < 0.0) {
- MelderInfo_writeLine (my constraints [jcons]. name.get(), U" >> ", my constraints [icons]. name.get());
- break;
- }
- }
- }
- }
- MelderInfo_close ();
- /*
- Remove room.
- */
- NUMvector_free <structOTGrammarFixedRanking> (my fixedRankings, 1); // dangle
- /*
- * Restore.
- */
- my numberOfFixedRankings --;
- my fixedRankings = savedFixedRankings; // undangle
- OTGrammar_restore (me);
- } catch (MelderError) {
- MelderInfo_close ();
- /*
- Remove room.
- */
- NUMvector_free <structOTGrammarFixedRanking> (my fixedRankings, 1); // dangle
- /*
- Restore.
- */
- my numberOfFixedRankings --;
- my fixedRankings = savedFixedRankings; // undangle
- OTGrammar_restore (me);
- Melder_throw (me, U": obligatory rankings not listed.");
- }
- }
- static void printConstraintNames (OTGrammar me, MelderString *buffer) {
- char32 text [200];
- bool secondLine = false;
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- if (str32chr (constraint -> name.get(), U'\n')) {
- char32 *newLine;
- str32cpy (text, constraint -> name.get());
- newLine = str32chr (text, U'\n');
- *newLine = U'\0';
- MelderString_append (buffer, U"\t", text);
- secondLine = true;
- } else {
- MelderString_append (buffer, U"\t", constraint -> name.get());
- }
- }
- MelderString_appendCharacter (buffer, U'\n');
- if (secondLine) {
- MelderString_appendCharacter (buffer, U'\t');
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- char32 *newLine = str32chr (constraint -> name.get(), U'\n');
- MelderString_append (buffer, U"\t", newLine ? newLine + 1 : U"");
- }
- MelderString_appendCharacter (buffer, U'\n');
- }
- }
- void OTGrammar_writeToHeaderlessSpreadsheetFile (OTGrammar me, MelderFile file) {
- try {
- autoMelderString buffer;
- MelderString_copy (& buffer, U"CONSTRAINTS\t");
- printConstraintNames (me, & buffer);
- MelderString_append (& buffer, U"rankings\t");
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- MelderString_append (& buffer, U"\t", constraint -> ranking);
- }
- MelderString_append (& buffer, U"\ndisharmonies\t");
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- OTGrammarConstraint constraint = & my constraints [my index [icons]];
- MelderString_append (& buffer, U"\t", constraint -> disharmony);
- }
- MelderString_appendCharacter (& buffer, U'\n');
- for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
- OTGrammarTableau tableau = & my tableaus [itab];
- integer winner = OTGrammar_getWinner (me, itab), numberOfOptimalCandidates = 0;
- for (integer icons = 1; icons <= my numberOfConstraints + 1; icons ++)
- MelderString_appendCharacter (& buffer, U'\t');
- MelderString_append (& buffer, U"\nINPUT\t", tableau -> input.get());
- printConstraintNames (me, & buffer);
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- if (OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0)
- numberOfOptimalCandidates ++;
- }
- for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
- OTGrammarCandidate candidate = & tableau -> candidates [icand];
- bool candidateIsOptimal = OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0;
- integer crucialCell = OTGrammar_crucialCell (me, itab, icand, winner, numberOfOptimalCandidates);
- MelderString_append (& buffer,
- candidateIsOptimal == false ? U"loser" : numberOfOptimalCandidates > 1 ? U"co-winner" : U"winner",
- U"\t",
- candidate -> output.get());
- for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
- integer index = my index [icons];
- OTGrammarConstraint constraint = & my constraints [index];
- static MelderString markString;
- MelderString_empty (& markString);
- /*
- An exclamation mark can be drawn in this cell only if all of the following conditions are met:
- 1. the candidate is not optimal;
- 2. the constraint is not tied;
- 3. this is the crucial cell, i.e. the cells after it are drawn in grey.
- */
- if (icons == crucialCell && ! candidateIsOptimal && ! constraint -> tiedToTheLeft && ! constraint -> tiedToTheRight) {
- const integer winnerMarks = tableau -> candidates [winner]. marks [index];
- for (integer imark = 1; imark <= winnerMarks + 1; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- MelderString_appendCharacter (& markString, U'!');
- for (integer imark = winnerMarks + 2; imark <= candidate -> marks [index]; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- } else {
- if (! candidateIsOptimal && (constraint -> tiedToTheLeft || constraint -> tiedToTheRight) &&
- crucialCell >= 1 && constraint -> disharmony == my constraints [my index [crucialCell]]. disharmony)
- {
- MelderString_appendCharacter (& markString, U'=');
- }
- for (integer imark = 1; imark <= candidate -> marks [index]; imark ++)
- MelderString_appendCharacter (& markString, U'*');
- }
- MelderString_append (& buffer, U"\t", markString.string);
- }
- MelderString_appendCharacter (& buffer, U'\n');
- }
- }
- MelderFile_writeText (file, buffer.string, Melder_getOutputEncoding ());
- } catch (MelderError) {
- Melder_throw (me, U": not saved to tab-separated file ", file, U".");
- }
- }
- /* End of file OTGrammar.cpp */
|