1 /* OTGrammar.cpp
2 *
3 * Copyright (C) 1997-2021 Paul Boersma
4 *
5 * This code is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or (at
8 * your option) any later version.
9 *
10 * This code is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this work. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /*
20 * pb 2002/07/16 GPL
21 * pb 2002/11/04 randomize in case of equal candidates
22 * pb 2003/05/08 better superset violation warning
23 * pb 2003/05/23 made superset violation warning conditional
24 * pb 2003/10/15 backtrack in case of failing multiple chews for EDCD
25 * pb 2003/10/15 crucial ties option
26 * pb 2004/01/17 OTGrammar_Distributions_getFractionCorrect
27 * pb 2004/08/08 OTGrammar_removeHarmonicallyBoundedCandidates
28 * pb 2004/08/09 bug removal: more complete OTGrammar_save and restore (affected multiple-chew correctness),
29 * changing the 114.5 in Boersma (Phonology 2003) to 118.1
30 * pb 2004/08/09 suppressed superset violation in case of identical constraint violation patterns such
31 * as for /(L L2) L (L2 L) (L1 L)/ and /(L2 L) L (L L2) (L1 L)/, thus restricting the warning to cases
32 * of *strict* superset violations
33 * pb 2004/08/11 repaired memory leak in OTGrammarTableau_removeCandidate_unstripped
34 * pb 2004/09/10 monitor rankings during learning from PairDistribution or Distributions
35 * pb 2004/10/16 struct structOTxx
36 * pb 2005/01/24 write to headerless spreadsheet file
37 * pb 2005/04/19 OTHistory
38 * pb 2005/06/30 learning from partial pairs
39 * pb 2005/12/11 OTGrammar_honourlocalRankings:
40 * pb 2005/12/11 OTGrammar_PairDistribution_listObligatoryRankings (depth 1)
41 * pb 2006/01/05 new decision strategies: HarmonicGrammar and LinearOT
42 * pb 2006/01/21 better procedure name
43 * pb 2006/02/02 new decision strategy: ExponentialHG
44 * pb 2006/12/08 MelderInfo
45 * pb 2007/04/22 multiply learning step by number of violations (for HarmonicGrammar and LinearOT)
46 * pb 2007/04/25 new decision strategy: MaximumEntropy
47 * pb 2007/04/30 many improvements
48 * pb 2007/05/20 new decision strategy: PositiveHG
49 * pb 2007/06/21 corrected PositiveHG
50 * pb 2007/06/21 made spreadsheet file readable as Table
51 * pb 2007/07/24 leak and constraint plasticity
52 * pb 2007/07/27 leak and constraint plasticity also written...
53 * pb 2007/08/08 wchar
54 * pb 2007/10/01 can write as encoding
55 * pb 2008/03/03 EDCD with vacation
56 * pb 2008/03/07 Demote one with vacation
57 * pb 2008/03/07 Reset to random total ranking
58 * pb 2008/03/27 Exponential HG: reset average weight to zero after every change
59 * pb 2008/03/28 Exponential HG: set update rule to HG-GLA rather than OT-GLA
60 * pb 2008/03/31 OTGrammar_PairDistribution_findPositiveWeights
61 * pb 2008/04/08 made (OTGrammar & Distributions) learnFromPartialOutputs and getFractionCorrect five times faster
62 * pb 2008/04/12 split off NUMlinprog
63 * pb 2008/05/31 new decision strategy: ExponentialMaximumEntropy
64 * pb 2009/03/09 new update rule: Weighted all up, highest down
65 * pb 2009/07/07 OTGrammar_PairDistribution_getMinimumNumberCorrect
66 * pb 2010/06/05 corrected colours
67 * pb 2011/03/01 renamed "strategy" to "updateRule", "meanLearningStep" to "plasticity", "rankingSpreading" to "evaluationNoise"
68 * pb 2011/03/22 C++
69 * pb 2011/04/27 Melder_debug 41 and 42
70 * pb 2011/07/14 C++
71 * pb 2014/02/27 skippable symmetric all
72 * pb 2014/07/25 RRIP
73 */
74
75 #include "OTGrammar.h"
76
77 #include "oo_DESTROY.h"
78 #include "OTGrammar_def.h"
79 #include "oo_COPY.h"
80 #include "OTGrammar_def.h"
81 #include "oo_EQUAL.h"
82 #include "OTGrammar_def.h"
83 #include "oo_CAN_WRITE_AS_ENCODING.h"
84 #include "OTGrammar_def.h"
85 #include "oo_WRITE_BINARY.h"
86 #include "OTGrammar_def.h"
87 #include "oo_READ_BINARY.h"
88 #include "OTGrammar_def.h"
89 #include "oo_DESCRIPTION.h"
90 #include "OTGrammar_def.h"
91
92 #include "enums_getText.h"
93 #include "OTGrammar_enums.h"
94 #include "enums_getValue.h"
95 #include "OTGrammar_enums.h"
96
v_info()97 void structOTGrammar :: v_info ()
98 {
99 structDaata :: v_info ();
100 integer numberOfCandidates = 0, numberOfViolations = 0;
101 for (integer itab = 1; itab <= numberOfTableaus; itab ++) {
102 numberOfCandidates += our tableaus [itab]. numberOfCandidates;
103 for (integer icand = 1; icand <= our tableaus [itab]. numberOfCandidates; icand ++)
104 for (integer icons = 1; icons <= our numberOfConstraints; icons ++)
105 numberOfViolations += our tableaus [itab]. candidates [icand]. marks [icons];
106 }
107 MelderInfo_writeLine (U"Decision strategy: ", kOTGrammar_decisionStrategy_getText (decisionStrategy));
108 MelderInfo_writeLine (U"Number of constraints: ", numberOfConstraints);
109 MelderInfo_writeLine (U"Number of tableaus: ", numberOfTableaus);
110 MelderInfo_writeLine (U"Number of candidates: ", numberOfCandidates);
111 MelderInfo_writeLine (U"Number of violation marks: ", numberOfViolations);
112 }
113
v_writeText(MelderFile file)114 void structOTGrammar :: v_writeText (MelderFile file) {
115 MelderFile_write (file, U"\n<", kOTGrammar_decisionStrategy_getText (decisionStrategy),
116 U">\n", leak, U" ! leak\n", our numberOfConstraints, U" constraints");
117 for (integer icons = 1; icons <= our numberOfConstraints; icons ++) {
118 OTGrammarConstraint constraint = & constraints [icons];
119 MelderFile_write (file, U"\nconstraint [", icons, U"]: \"");
120 for (const char32 *p = & constraint -> name [0]; *p; p ++) {
121 if (*p == U'\"')
122 MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
123 MelderFile_writeCharacter (file, *p);
124 }
125 MelderFile_write (file, U"\" ", constraint -> ranking,
126 U" ", constraint -> disharmony, U" ", constraint -> plasticity, U" ! ");
127 for (const char32 *p = & constraint -> name [0]; *p; p ++) {
128 if (*p == U'\n')
129 MelderFile_writeCharacter (file, U' ');
130 else if (*p == U'\\' && p [1] == U's' && p [2] == U'{')
131 p += 2;
132 else if (*p == U'}')
133 { }
134 else
135 MelderFile_writeCharacter (file, *p);
136 }
137 }
138 MelderFile_write (file, U"\n\n", our numberOfFixedRankings, U" fixed rankings");
139 for (integer irank = 1; irank <= our numberOfFixedRankings; irank ++) {
140 OTGrammarFixedRanking fixedRanking = & our fixedRankings [irank];
141 MelderFile_write (file, U"\n ", fixedRanking -> higher, U" ", fixedRanking -> lower);
142 }
143 MelderFile_write (file, U"\n\n", our numberOfTableaus, U" tableaus");
144 for (integer itab = 1; itab <= our numberOfTableaus; itab ++) {
145 OTGrammarTableau tableau = & our tableaus [itab];
146 MelderFile_write (file, U"\ninput [", itab, U"]: \"");
147 for (const char32 *p = & tableau -> input [0]; *p; p ++) {
148 if (*p == U'\"')
149 MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
150 MelderFile_writeCharacter (file, *p);
151 }
152 MelderFile_write (file, U"\" ", tableau -> numberOfCandidates);
153 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
154 OTGrammarCandidate candidate = & tableau -> candidates [icand];
155 MelderFile_write (file, U"\n candidate [", icand, U"]: \"");
156 for (const char32 *p = & candidate -> output [0]; *p; p ++) {
157 if (*p == U'\"')
158 MelderFile_writeCharacter (file, U'\"'); // double any quotes within quotes
159 MelderFile_writeCharacter (file, *p);
160 }
161 MelderFile_writeCharacter (file, U'\"');
162 for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++) {
163 MelderFile_write (file, U" ", candidate -> marks [icons]);
164 }
165 }
166 }
167 }
168
OTGrammar_checkIndex(OTGrammar me)169 void OTGrammar_checkIndex (OTGrammar me) {
170 if (my index.size != 0) return;
171 my index = to_INTVEC (my numberOfConstraints);
172 OTGrammar_sort (me);
173 }
174
v_readText(MelderReadText text,int formatVersion)175 void structOTGrammar :: v_readText (MelderReadText text, int formatVersion) {
176 OTGrammar_Parent :: v_readText (text, formatVersion);
177 if (formatVersion >= 1) {
178 try {
179 our decisionStrategy = (kOTGrammar_decisionStrategy) texgete8 (text, (enum_generic_getValue) kOTGrammar_decisionStrategy_getValue);
180 } catch (MelderError) {
181 Melder_throw (U"Trying to read decision strategy.");
182 }
183 }
184 if (formatVersion >= 2) {
185 try {
186 our leak = texgetr64 (text);
187 } catch (MelderError) {
188 Melder_throw (U"Trying to read leak.");
189 }
190 }
191 try {
192 our numberOfConstraints = texgeti32 (text);
193 } catch (MelderError) {
194 Melder_throw (U"Trying to read number of constraints.");
195 }
196 if (our numberOfConstraints < 1)
197 Melder_throw (U"No constraints.");
198 our constraints = newvectorzero <structOTGrammarConstraint> (our numberOfConstraints);
199 for (integer icons = 1; icons <= our numberOfConstraints; icons ++) {
200 OTGrammarConstraint constraint = & constraints [icons];
201 try {
202 constraint -> name = texgetw16 (text);
203 } catch (MelderError) {
204 Melder_throw (U"Trying to read name of constraint ", icons, U".");
205 }
206 try {
207 constraint -> ranking = texgetr64 (text);
208 } catch (MelderError) {
209 Melder_throw (U"Trying to read ranking of constraint ", icons, U".");
210 }
211 try {
212 constraint -> disharmony = texgetr64 (text);
213 } catch (MelderError) {
214 Melder_throw (U"Trying to read disharmony of constraint ", icons, U".");
215 }
216 if (formatVersion < 2) {
217 constraint -> plasticity = 1.0;
218 } else {
219 try {
220 constraint -> plasticity = texgetr64 (text);
221 } catch (MelderError) {
222 Melder_throw (U"Trying to read plasticity of constraint ", icons, U".");
223 }
224 }
225 }
226 try {
227 our numberOfFixedRankings = texgeti32 (text);
228 } catch (MelderError) {
229 Melder_throw (U"Trying to read number of fixed rankings.");
230 }
231 if (our numberOfFixedRankings >= 1) {
232 our fixedRankings = newvectorzero <structOTGrammarFixedRanking> (numberOfFixedRankings);
233 for (integer irank = 1; irank <= our numberOfFixedRankings; irank ++) {
234 OTGrammarFixedRanking fixedRanking = & our fixedRankings [irank];
235 try {
236 fixedRanking -> higher = texgeti32 (text);
237 } catch (MelderError) {
238 Melder_throw (U"Trying to read the higher of constraint pair ", irank, U".");
239 }
240 try {
241 fixedRanking -> lower = texgeti32 (text);
242 } catch (MelderError) {
243 Melder_throw (U"Trying to read the lower of constraint pair ", irank, U".");
244 }
245 }
246 }
247 try {
248 our numberOfTableaus = texgeti32 (text);
249 } catch (MelderError) {
250 Melder_throw (U"Trying to read number of tableaus.");
251 }
252 if (our numberOfTableaus < 1)
253 Melder_throw (U"No tableaus.");
254 our tableaus = newvectorzero <structOTGrammarTableau> (numberOfTableaus);
255 for (integer itab = 1; itab <= our numberOfTableaus; itab ++) {
256 OTGrammarTableau tableau = & our tableaus [itab];
257 try {
258 tableau -> input = texgetw16 (text);
259 } catch (MelderError) {
260 Melder_throw (U"Trying to read input of tableau ", itab, U".");
261 }
262 try {
263 tableau -> numberOfCandidates = texgeti32 (text);
264 } catch (MelderError) {
265 Melder_throw (U"Trying to read number of candidates of tableau ", itab, U".");
266 }
267 Melder_require (tableau -> numberOfCandidates > 0,
268 U"No candidates in tableau ", itab,
269 U" (input: ", tableau -> input.get(), U")"
270 U" in line ", MelderReadText_getLineNumber (text),
271 itab == 1 ? U"." : U", or perhaps wrong number of candidates for input «",
272 itab == 1 ? nullptr : our tableaus [itab - 1]. input.get(),
273 itab == 1 ? nullptr : U"»."
274 );
275 tableau -> candidates = newvectorzero <structOTGrammarCandidate> (tableau -> numberOfCandidates);
276 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
277 OTGrammarCandidate candidate = & tableau -> candidates [icand];
278 try {
279 candidate -> output = texgetw16 (text);
280 } catch (MelderError) {
281 Melder_throw (U"Trying to read candidate ", icand, U" of tableau ", itab,
282 U" (input: ", tableau -> input.get(), U") in line ", MelderReadText_getLineNumber (text), U".");
283 }
284 candidate -> numberOfConstraints = numberOfConstraints; // redundancy, needed for writing binary
285 candidate -> marks = zero_INTVEC (candidate -> numberOfConstraints);
286 for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++) {
287 try {
288 candidate -> marks [icons] = texgeti16 (text);
289 } catch (MelderError) {
290 Melder_throw
291 (U"Trying to read number of violations of constraint ", icons,
292 U" (", our constraints [icons]. name.get(), U")"
293 U" of candidate ", icand,
294 U" (", candidate -> output.get(), U")"
295 U" of tableau ", itab,
296 U" (input: ", tableau -> input.get(), U")"
297 U" in line ", MelderReadText_getLineNumber (text), U".");
298 }
299 }
300 }
301 }
302 OTGrammar_checkIndex (this);
303 }
304
305 Thing_implement (OTGrammar, Daata, 2);
306
307 Thing_implement (OTHistory, TableOfReal, 0);
308
OTGrammar_sort(OTGrammar me)309 void OTGrammar_sort (OTGrammar me) {
310 std::sort (my index.begin(), my index.end(),
311 [me] (integer icons, integer jcons) {
312 OTGrammarConstraint ci = & my constraints [icons], cj = & my constraints [jcons];
313 /*
314 Sort primarily by disharmony.
315 */
316 if (ci -> disharmony > cj -> disharmony)
317 return true;
318 if (ci -> disharmony < cj -> disharmony)
319 return false;
320 /*
321 Tied constraints are sorted alphabetically.
322 */
323 return str32cmp (my constraints [icons]. name.get(), my constraints [jcons]. name.get()) < 0;
324 }
325 );
326 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
327 OTGrammarConstraint constraint = & my constraints [my index [icons]];
328 constraint -> tiedToTheLeft = ( icons > 1 &&
329 my constraints [my index [icons - 1]]. disharmony == constraint -> disharmony );
330 constraint -> tiedToTheRight = ( icons < my numberOfConstraints &&
331 my constraints [my index [icons + 1]]. disharmony == constraint -> disharmony );
332 }
333 }
334
OTGrammar_newDisharmonies(OTGrammar me,double spreading)335 void OTGrammar_newDisharmonies (OTGrammar me, double spreading) {
336 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
337 OTGrammarConstraint constraint = & my constraints [icons];
338 constraint -> disharmony = constraint -> ranking + NUMrandomGauss (0, spreading)
339 /*NUMrandomUniform (-spreading, spreading)*/;
340 }
341 OTGrammar_sort (me);
342 }
343
OTGrammar_getTableau(OTGrammar me,conststring32 input)344 integer OTGrammar_getTableau (OTGrammar me, conststring32 input) {
345 for (integer itab = 1; itab <= my numberOfTableaus; itab ++)
346 if (str32equ (my tableaus [itab]. input.get(), input))
347 return itab;
348 Melder_throw (U"Input \"", input, U"\" not in list of tableaus.");
349 }
350
_OTGrammar_fillInHarmonies(OTGrammar me,integer itab)351 static void _OTGrammar_fillInHarmonies (OTGrammar me, integer itab) noexcept {
352 if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY)
353 return;
354 OTGrammarTableau tableau = & my tableaus [itab];
355 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
356 OTGrammarCandidate candidate = & tableau -> candidates [icand];
357 INTVEC marks = candidate -> marks.get();
358 longdouble disharmony = 0.0;
359 if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
360 my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
361 {
362 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
363 disharmony += my constraints [icons]. disharmony * marks [icons];
364 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
365 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
366 {
367 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
368 disharmony += exp (my constraints [icons]. disharmony) * marks [icons];
369 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
370 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
371 if (my constraints [icons]. disharmony > 0.0)
372 disharmony += my constraints [icons]. disharmony * marks [icons];
373 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
374 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
375 const double constraintDisharmony = std::max (my constraints [icons]. disharmony, 1.0);
376 disharmony += constraintDisharmony * marks [icons];
377 }
378 } else {
379 Melder_fatal (U"_OTGrammar_fillInHarmonies: unimplemented decision strategy.");
380 }
381 candidate -> harmony = - (double) disharmony;
382 }
383 }
384
OTGrammar_compareCandidates(OTGrammar me,integer itab1,integer icand1,integer itab2,integer icand2)385 int OTGrammar_compareCandidates (OTGrammar me, integer itab1, integer icand1, integer itab2, integer icand2) noexcept {
386 INTVEC marks1 = my tableaus [itab1]. candidates [icand1]. marks.get();
387 INTVEC marks2 = my tableaus [itab2]. candidates [icand2]. marks.get();
388 if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
389 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
390 integer numberOfMarks1 = marks1 [my index [icons]];
391 integer numberOfMarks2 = marks2 [my index [icons]];
392 /*
393 Count tied constraints as one.
394 */
395 while (my constraints [my index [icons]]. tiedToTheRight) {
396 icons ++;
397 numberOfMarks1 += marks1 [my index [icons]];
398 numberOfMarks2 += marks2 [my index [icons]];
399 }
400 if (numberOfMarks1 < numberOfMarks2)
401 return -1; // candidate 1 is better than candidate 2
402 if (numberOfMarks1 > numberOfMarks2)
403 return +1; // candidate 2 is better than candidate 1
404 }
405 /*
406 If we arrive here, none of the comparisons found a difference
407 between the two candidates. Hence, they are equally good.
408 */
409 return 0;
410 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
411 my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
412 {
413 double disharmony1 = 0.0, disharmony2 = 0.0;
414 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
415 disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
416 disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
417 }
418 if (disharmony1 < disharmony2)
419 return -1; // candidate 1 is better than candidate 2
420 if (disharmony1 > disharmony2)
421 return +1; // candidate 2 is better than candidate 1
422 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
423 double disharmony1 = 0.0, disharmony2 = 0.0;
424 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
425 if (my constraints [icons]. disharmony > 0.0) {
426 disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
427 disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
428 }
429 }
430 if (disharmony1 < disharmony2)
431 return -1; // candidate 1 is better than candidate 2
432 if (disharmony1 > disharmony2)
433 return +1; // candidate 2 is better than candidate 1
434 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
435 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
436 {
437 double disharmony1 = 0.0, disharmony2 = 0.0;
438 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
439 disharmony1 += exp (my constraints [icons]. disharmony) * marks1 [icons];
440 disharmony2 += exp (my constraints [icons]. disharmony) * marks2 [icons];
441 }
442 if (disharmony1 < disharmony2)
443 return -1; // candidate 1 is better than candidate 2
444 if (disharmony1 > disharmony2)
445 return +1; // candidate 2 is better than candidate 1
446 } else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
447 double disharmony1 = 0.0, disharmony2 = 0.0;
448 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
449 double constraintDisharmony = std::max (my constraints [icons]. disharmony, 1.0);
450 disharmony1 += constraintDisharmony * marks1 [icons];
451 disharmony2 += constraintDisharmony * marks2 [icons];
452 }
453 if (disharmony1 < disharmony2)
454 return -1; // candidate 1 is better than candidate 2
455 if (disharmony1 > disharmony2)
456 return +1; // candidate 2 is better than candidate 1
457 } else
458 Melder_fatal (U"Unimplemented decision strategy.");
459 return 0; // the two total disharmonies are equal
460 }
461
_OTGrammar_fillInProbabilities(OTGrammar me,integer itab)462 static void _OTGrammar_fillInProbabilities (OTGrammar me, integer itab) noexcept {
463 OTGrammarTableau tableau = & my tableaus [itab];
464 double maximumHarmony = tableau -> candidates [1]. harmony;
465 for (integer icand = 2; icand <= tableau -> numberOfCandidates; icand ++) {
466 OTGrammarCandidate candidate = & tableau -> candidates [icand];
467 if (candidate -> harmony > maximumHarmony)
468 maximumHarmony = candidate -> harmony;
469 }
470 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
471 OTGrammarCandidate candidate = & tableau -> candidates [icand];
472 candidate -> probability = exp (candidate -> harmony - maximumHarmony);
473 Melder_assert (candidate -> probability >= 0.0 && candidate -> probability <= 1.0);
474 }
475 longdouble sumOfProbabilities = 0.0;
476 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
477 OTGrammarCandidate candidate = & tableau -> candidates [icand];
478 sumOfProbabilities += candidate -> probability;
479 }
480 Melder_assert (sumOfProbabilities > 0.0); // because at least one of them is 1.0
481 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
482 OTGrammarCandidate candidate = & tableau -> candidates [icand];
483 candidate -> probability /= double (sumOfProbabilities);
484 }
485 }
486
OTGrammar_getWinner(OTGrammar me,integer itab)487 integer OTGrammar_getWinner (OTGrammar me, integer itab) noexcept {
488 integer icand_best = 1;
489 if (my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
490 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
491 {
492 _OTGrammar_fillInHarmonies (me, itab);
493 _OTGrammar_fillInProbabilities (me, itab);
494 double cutOff = NUMrandomUniform (0.0, 1.0);
495 longdouble sumOfProbabilities = 0.0;
496 for (integer icand = 1; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
497 sumOfProbabilities += my tableaus [itab]. candidates [icand]. probability;
498 if (sumOfProbabilities > cutOff) {
499 icand_best = icand;
500 break;
501 }
502 }
503 } else {
504 integer numberOfBestCandidates = 1;
505 for (integer icand = 2; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
506 int comparison = OTGrammar_compareCandidates (me, itab, icand, itab, icand_best);
507 if (comparison == -1) {
508 icand_best = icand; // the current candidate is the unique best candidate found so far
509 numberOfBestCandidates = 1;
510 } else if (comparison == 0) {
511 numberOfBestCandidates += 1; // the current candidate is equally good as the best found before
512 /*
513 Give all candidates that are equally good an equal chance to become the winner.
514 */
515 if (Melder_debug == 41) {
516 icand_best = icand_best; // keep first
517 } else if (Melder_debug == 42) {
518 icand_best = icand; // take last
519 } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
520 icand_best = icand;
521 }
522 }
523 }
524 }
525 return icand_best;
526 }
527
OTGrammar_getNumberOfOptimalCandidates(OTGrammar me,integer itab)528 integer OTGrammar_getNumberOfOptimalCandidates (OTGrammar me, integer itab) {
529 if (my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
530 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY) return 1;
531 integer icand_best = 1, numberOfBestCandidates = 1;
532 for (integer icand = 2; icand <= my tableaus [itab]. numberOfCandidates; icand ++) {
533 int comparison = OTGrammar_compareCandidates (me, itab, icand, itab, icand_best);
534 if (comparison == -1) {
535 icand_best = icand; // the current candidate is the best candidate found so far
536 numberOfBestCandidates = 1;
537 } else if (comparison == 0) {
538 numberOfBestCandidates += 1; // the current candidate is equally good as the best found before
539 }
540 }
541 return numberOfBestCandidates;
542 }
543
OTGrammar_isCandidateGrammatical(OTGrammar me,integer itab,integer icand)544 bool OTGrammar_isCandidateGrammatical (OTGrammar me, integer itab, integer icand) {
545 for (integer jcand = 1; jcand <= my tableaus [itab]. numberOfCandidates; jcand ++)
546 if (jcand != icand && OTGrammar_compareCandidates (me, itab, jcand, itab, icand) < 0)
547 return false;
548 return true;
549 }
550
OTGrammar_isCandidateSinglyGrammatical(OTGrammar me,integer itab,integer icand)551 bool OTGrammar_isCandidateSinglyGrammatical (OTGrammar me, integer itab, integer icand) {
552 for (integer jcand = 1; jcand <= my tableaus [itab]. numberOfCandidates; jcand ++)
553 if (jcand != icand && OTGrammar_compareCandidates (me, itab, jcand, itab, icand) <= 0)
554 return false;
555 return true;
556 }
557
OTGrammar_getInterpretiveParse(OTGrammar me,conststring32 partialOutput,integer * out_bestTableau,integer * out_bestCandidate)558 void OTGrammar_getInterpretiveParse (OTGrammar me, conststring32 partialOutput, integer *out_bestTableau, integer *out_bestCandidate) {
559 try {
560 integer itab_best = 0, icand_best = 0, numberOfBestCandidates = 0;
561 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
562 OTGrammarTableau tableau = & my tableaus [itab];
563 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
564 OTGrammarCandidate cand = & tableau -> candidates [icand];
565 if (str32str (cand -> output.get(), partialOutput)) { // T&S' idea of surface->overt mapping
566 if (itab_best == 0) {
567 itab_best = itab; // the first compatible input/output pair found is the first guess for the best candidate
568 icand_best = icand;
569 numberOfBestCandidates = 1;
570 } else {
571 int comparison = OTGrammar_compareCandidates (me, itab, icand, itab_best, icand_best);
572 if (comparison == -1) {
573 itab_best = itab; // the current input/output pair is the best candidate found so far
574 icand_best = icand;
575 numberOfBestCandidates = 1;
576 } else if (comparison == 0) {
577 numberOfBestCandidates += 1; // the current input/output pair is equally good as the best found before
578 /*
579 * Give all candidates that are equally good an equal chance to become the winner.
580 */
581 if (Melder_debug == 41) {
582 itab_best = itab_best;
583 icand_best = icand_best; // keep first
584 } else if (Melder_debug == 42) {
585 itab_best = itab;
586 icand_best = icand; // take last
587 } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
588 itab_best = itab;
589 icand_best = icand;
590 }
591 }
592 }
593 }
594 }
595 }
596 if (itab_best == 0)
597 Melder_throw (U"The partial output \"", partialOutput, U"\" does not match any candidate for any input form.");
598 if (out_bestTableau)
599 *out_bestTableau = itab_best;
600 if (out_bestCandidate)
601 *out_bestCandidate = icand_best;
602 } catch (MelderError) {
603 Melder_throw (U"Interpretive parse not computed.");
604 }
605 }
606
OTGrammar_getInterpretiveParse_opt(OTGrammar me,integer ipartialOutput,integer * out_bestTableau,integer * out_bestCandidate)607 static void OTGrammar_getInterpretiveParse_opt (OTGrammar me, integer ipartialOutput, integer *out_bestTableau, integer *out_bestCandidate) {
608 try {
609 integer itab_best = 0, icand_best = 0, numberOfBestCandidates = 0;
610 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
611 OTGrammarTableau tableau = & my tableaus [itab];
612 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
613 OTGrammarCandidate cand = & tableau -> candidates [icand];
614 if (cand -> partialOutputMatches [ipartialOutput]) { // T&S' idea of surface->overt mapping
615 if (itab_best == 0) {
616 itab_best = itab; // the first compatible input/output pair found is the first guess for the best candidate
617 icand_best = icand;
618 numberOfBestCandidates = 1;
619 } else {
620 int comparison = OTGrammar_compareCandidates (me, itab, icand, itab_best, icand_best);
621 if (comparison == -1) {
622 itab_best = itab; // the current input/output pair is the best candidate found so far
623 icand_best = icand;
624 numberOfBestCandidates = 1;
625 } else if (comparison == 0) {
626 numberOfBestCandidates += 1; // the current input/output pair is equally good as the best found before
627 /*
628 * Give all candidates that are equally good an equal chance to become the winner.
629 */
630 if (Melder_debug == 41) {
631 itab_best = itab_best;
632 icand_best = icand_best; // keep first
633 } else if (Melder_debug == 42) {
634 itab_best = itab;
635 icand_best = icand; // take last
636 } else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0) { // default: take random
637 itab_best = itab;
638 icand_best = icand;
639 }
640 }
641 }
642 }
643 }
644 }
645 Melder_assert (itab_best != 0);
646 if (out_bestTableau)
647 *out_bestTableau = itab_best;
648 if (out_bestCandidate)
649 *out_bestCandidate = icand_best;
650 } catch (MelderError) {
651 Melder_throw (U"Interpretive parse not computed.");
652 }
653 }
654
OTGrammar_isPartialOutputGrammatical(OTGrammar me,conststring32 partialOutput)655 bool OTGrammar_isPartialOutputGrammatical (OTGrammar me, conststring32 partialOutput) {
656 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
657 OTGrammarTableau tableau = & my tableaus [itab];
658 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
659 if (str32str (tableau -> candidates [icand]. output.get(), partialOutput))
660 if (OTGrammar_isCandidateGrammatical (me, itab, icand))
661 return true;
662 }
663 }
664 return false;
665 }
666
OTGrammar_areAllPartialOutputsGrammatical(OTGrammar me,Strings thee)667 bool OTGrammar_areAllPartialOutputsGrammatical (OTGrammar me, Strings thee) {
668 for (integer ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
669 conststring32 partialOutput = thy strings [ioutput].get();
670 if (! OTGrammar_isPartialOutputGrammatical (me, partialOutput))
671 return false;
672 }
673 return true;
674 }
675
OTGrammar_isPartialOutputSinglyGrammatical(OTGrammar me,conststring32 partialOutput)676 bool OTGrammar_isPartialOutputSinglyGrammatical (OTGrammar me, conststring32 partialOutput) {
677 bool found = false;
678 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
679 OTGrammarTableau tableau = & my tableaus [itab];
680 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
681 if (str32str (tableau -> candidates [icand]. output.get(), partialOutput)) {
682 if (OTGrammar_isCandidateGrammatical (me, itab, icand)) {
683 found = true;
684 /*
685 All other grammatical candidates should match.
686 */
687 for (integer jcand = 1; jcand <= tableau -> numberOfCandidates; jcand ++) {
688 if (OTGrammar_compareCandidates (me, itab, jcand, itab, icand) == 0)
689 if (! str32str (tableau -> candidates [jcand]. output.get(), partialOutput))
690 return false; // partial output is multiply optimal
691 }
692 }
693 }
694 }
695 }
696 return found;
697 }
698
OTGrammar_areAllPartialOutputsSinglyGrammatical(OTGrammar me,Strings thee)699 bool OTGrammar_areAllPartialOutputsSinglyGrammatical (OTGrammar me, Strings thee) {
700 for (integer ioutput = 1; ioutput <= thy numberOfStrings; ioutput ++) {
701 conststring32 partialOutput = thy strings [ioutput].get();
702 if (! OTGrammar_isPartialOutputSinglyGrammatical (me, partialOutput))
703 return false;
704 }
705 return true;
706 }
707
OTGrammar_crucialCell(OTGrammar me,integer itab,integer icand,integer iwinner,integer numberOfOptimalCandidates)708 static integer OTGrammar_crucialCell (OTGrammar me, integer itab, integer icand, integer iwinner, integer numberOfOptimalCandidates) {
709 OTGrammarTableau tableau = & my tableaus [itab];
710 if (tableau -> numberOfCandidates < 2) return 0; // if there is only one candidate, all cells can be greyed
711 if (my decisionStrategy != kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) return my numberOfConstraints; // nothing grey
712 if (OTGrammar_compareCandidates (me, itab, icand, itab, iwinner) == 0) { // candidate equally good as winner?
713 if (numberOfOptimalCandidates > 1) {
714 /* All cells are important. */
715 } else {
716 integer secondBest = 0;
717 for (integer jcand = 1; jcand <= tableau -> numberOfCandidates; jcand ++) {
718 if (OTGrammar_compareCandidates (me, itab, jcand, itab, iwinner) != 0) { // a non-optimal candidate?
719 if (secondBest == 0) {
720 secondBest = jcand; // first guess
721 } else if (OTGrammar_compareCandidates (me, itab, jcand, itab, secondBest) < 0) {
722 secondBest = jcand; // better guess
723 }
724 }
725 }
726 if (secondBest == 0) return 0; // if all candidates are equally good, all cells can be greyed
727 return OTGrammar_crucialCell (me, itab, secondBest, iwinner, 1);
728 }
729 } else {
730 const constINTVEC candidateMarks = tableau -> candidates [icand]. marks.get();
731 const constINTVEC winnerMarks = tableau -> candidates [iwinner]. marks.get();
732 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
733 integer numberOfCandidateMarks = candidateMarks [my index [icons]];
734 integer numberOfWinnerMarks = winnerMarks [my index [icons]];
735 while (my constraints [my index [icons]]. tiedToTheRight) {
736 icons ++;
737 numberOfCandidateMarks += candidateMarks [my index [icons]];
738 numberOfWinnerMarks += winnerMarks [my index [icons]];
739 }
740 if (numberOfCandidateMarks > numberOfWinnerMarks)
741 return icons;
742 }
743 }
744 return my numberOfConstraints; // nothing grey
745 }
746
OTGrammar_constraintWidth(Graphics g,conststring32 name)747 static double OTGrammar_constraintWidth (Graphics g, conststring32 name) {
748 char32 text [100];
749 str32cpy (text, name);
750 char32 *newLine = str32chr (text, U'\n');
751 if (newLine) {
752 double firstWidth, secondWidth;
753 *newLine = U'\0';
754 firstWidth = Graphics_textWidth (g, text);
755 secondWidth = Graphics_textWidth (g, newLine + 1);
756 return firstWidth > secondWidth ? firstWidth : secondWidth;
757 }
758 return Graphics_textWidth (g, text);
759 }
760
OTGrammar_drawTableau(OTGrammar me,Graphics g,bool vertical,conststring32 input)761 void OTGrammar_drawTableau (OTGrammar me, Graphics g, bool vertical, conststring32 input) {
762 try {
763 const double fontSize = Graphics_inqFontSize (g);
764 MelderColour colour = Graphics_inqColour (g);
765 const integer itab = OTGrammar_getTableau (me, input);
766 _OTGrammar_fillInHarmonies (me, itab);
767 const integer winner = OTGrammar_getWinner (me, itab);
768
769 Graphics_setWindow (g, 0.0, 1.0, 0.0, 1.0);
770 const double margin = Graphics_dxMMtoWC (g, 1.0);
771 const double fingerWidth = Graphics_dxMMtoWC (g, 7.0) * fontSize / 12.0;
772 const double doubleLineDx = Graphics_dxMMtoWC (g, 0.9);
773 const double doubleLineDy = Graphics_dyMMtoWC (g, 0.9);
774 const double rowHeight = Graphics_dyMMtoWC (g, 1.5 * fontSize * 25.4 / 72);
775 const double descent = rowHeight * 0.5;
776 const double worldAspectRatio = Graphics_dyMMtoWC (g, 1.0) / Graphics_dxMMtoWC (g, 1.0); // because Graphics_textWidth measures in the x direction only
777 /*
778 Compute the height of the header row.
779 */
780 double headerHeight;
781 if (vertical) {
782 headerHeight = 0.0;
783 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
784 const OTGrammarConstraint constraint = & my constraints [icons];
785 const double constraintTextWidth = Graphics_textWidth (g, constraint -> name.get());
786 if (constraintTextWidth > headerHeight)
787 headerHeight = constraintTextWidth;
788 }
789 headerHeight += margin * 2;
790 headerHeight *= worldAspectRatio;
791 } else {
792 headerHeight = rowHeight;
793 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
794 OTGrammarConstraint constraint = & my constraints [icons];
795 if (str32chr (constraint -> name.get(), U'\n')) {
796 headerHeight *= 1.6;
797 break;
798 }
799 }
800 }
801 /*
802 Compute longest candidate string.
803 Also count the number of optimal candidates (if there are more than one, the fingers will be drawn in red).
804 */
805 double candWidth = Graphics_textWidth (g, input);
806 OTGrammarTableau tableau = & my tableaus [itab];
807 integer numberOfOptimalCandidates = 0;
808 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
809 double width = Graphics_textWidth (g, tableau -> candidates [icand]. output.get());
810 if (OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0) {
811 width += fingerWidth;
812 numberOfOptimalCandidates ++;
813 }
814 if (width > candWidth)
815 candWidth = width;
816 }
817 candWidth += margin * 3;
818 /*
819 Compute tableau width.
820 */
821 double tableauWidth = candWidth + doubleLineDx;
822 if (vertical) {
823 tableauWidth += rowHeight * my numberOfConstraints / worldAspectRatio;
824 } else {
825 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
826 OTGrammarConstraint constraint = & my constraints [icons];
827 tableauWidth += OTGrammar_constraintWidth (g, constraint -> name.get());
828 }
829 tableauWidth += margin * 2 * my numberOfConstraints;
830 }
831 /*
832 Draw box.
833 */
834 double x = doubleLineDx; // left side of tableau
835 double y = 1.0 - doubleLineDy;
836 Graphics_rectangle (g, x, x + tableauWidth,
837 y - headerHeight - tableau -> numberOfCandidates * rowHeight - doubleLineDy, y);
838 /*
839 Draw input.
840 */
841 y -= headerHeight;
842 Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
843 Graphics_text (g, x + 0.5 * candWidth, y + 0.5 * headerHeight, input);
844 Graphics_rectangle (g, x, x + candWidth, y, y + headerHeight);
845 /*
846 Draw constraint names.
847 */
848 x += candWidth + doubleLineDx;
849 if (vertical)
850 Graphics_setTextRotation (g, 90.0);
851 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
852 OTGrammarConstraint constraint = & my constraints [my index [icons]];
853 double width = vertical ? rowHeight / worldAspectRatio : OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2;
854 if (str32chr (constraint -> name.get(), U'\n') && ! vertical) {
855 autoMelderString text;
856 MelderString_copy (& text, constraint -> name.get());
857 char32 *newLine = str32chr (text.string, U'\n');
858 *newLine = U'\0';
859 Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_TOP);
860 Graphics_text (g, x + 0.5 * width, y + headerHeight, text.string);
861 Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
862 Graphics_text (g, x + 0.5 * width, y, newLine + 1);
863 } else if (vertical) {
864 Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
865 Graphics_text (g, x + 0.5 * width, y + margin, constraint -> name.get());
866 } else {
867 Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
868 Graphics_text (g, x + 0.5 * width, y + 0.5 * headerHeight, constraint -> name.get());
869 }
870 if (constraint -> tiedToTheLeft)
871 Graphics_setLineType (g, Graphics_DOTTED);
872 Graphics_line (g, x, y, x, y + headerHeight);
873 Graphics_setLineType (g, Graphics_DRAWN);
874 Graphics_line (g, x, y, x + width, y);
875 x += width;
876 }
877 if (vertical) Graphics_setTextRotation (g, 0.0);
878 /*
879 Draw candidates.
880 */
881 y -= doubleLineDy;
882 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
883 integer crucialCell = OTGrammar_crucialCell (me, itab, icand, winner, numberOfOptimalCandidates);
884 bool candidateIsOptimal = OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0;
885 /*
886 Draw candidate transcription.
887 */
888 x = doubleLineDx;
889 y -= rowHeight;
890 Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
891 Graphics_text (g, x + candWidth - margin, y + descent, tableau -> candidates [icand]. output.get());
892 if (candidateIsOptimal) {
893 Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
894 Graphics_setFontSize (g, 1.5 * fontSize);
895 if (numberOfOptimalCandidates > 1) Graphics_setColour (g, Melder_RED);
896 Graphics_text (g, x + margin, y + descent - Graphics_dyMMtoWC (g, 1.0) * fontSize / 12.0, U"☞");
897 Graphics_setColour (g, colour);
898 Graphics_setFontSize (g, fontSize);
899 }
900 Graphics_rectangle (g, x, x + candWidth, y, y + rowHeight);
901 /*
902 Draw grey cell backgrounds.
903 */
904 x = candWidth + 2 * doubleLineDx;
905 Graphics_setGrey (g, 0.9);
906 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
907 const integer index = my index [icons];
908 OTGrammarConstraint constraint = & my constraints [index];
909 const double width = ( vertical ? rowHeight / worldAspectRatio :
910 OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2 );
911 if (icons > crucialCell)
912 Graphics_fillRectangle (g, x, x + width, y, y + rowHeight);
913 x += width;
914 }
915 Graphics_setColour (g, colour);
916 /*
917 Draw cell marks.
918 */
919 x = candWidth + 2 * doubleLineDx;
920 Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
921 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
922 const integer index = my index [icons];
923 OTGrammarConstraint constraint = & my constraints [index];
924 const double width = vertical ? rowHeight / worldAspectRatio : OTGrammar_constraintWidth (g, constraint -> name.get()) + margin * 2;
925 static MelderString markString;
926 MelderString_empty (& markString);
927 if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
928 /*
929 An exclamation mark can be drawn in this cell only if all of the following conditions are met:
930 1. the candidate is not optimal;
931 2. the constraint is not tied;
932 3. this is the crucial cell, i.e. the cells after it are drawn in grey.
933 */
934 if (icons == crucialCell && ! candidateIsOptimal && ! constraint -> tiedToTheLeft && ! constraint -> tiedToTheRight) {
935 const integer winnerMarks = tableau -> candidates [winner]. marks [index];
936 for (integer imark = 1; imark <= winnerMarks + 1; imark ++)
937 MelderString_appendCharacter (& markString, U'*');
938 for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
939 MelderString_appendCharacter (& markString, U'+');
940 MelderString_appendCharacter (& markString, U'!');
941 for (integer imark = winnerMarks + 2; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
942 MelderString_appendCharacter (& markString, U'*');
943 } else {
944 if (! candidateIsOptimal && (constraint -> tiedToTheLeft || constraint -> tiedToTheRight) &&
945 crucialCell >= 1 && constraint -> disharmony == my constraints [my index [crucialCell]]. disharmony)
946 {
947 Graphics_setColour (g, Melder_RED);
948 }
949 for (integer imark = 1; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
950 MelderString_appendCharacter (& markString, U'*');
951 for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
952 MelderString_appendCharacter (& markString, U'+');
953 }
954 } else {
955 for (integer imark = 1; imark <= tableau -> candidates [icand]. marks [index]; imark ++)
956 MelderString_appendCharacter (& markString, U'*');
957 for (integer imark = tableau -> candidates [icand]. marks [index]; imark < 0; imark ++)
958 MelderString_appendCharacter (& markString, U'+');
959 }
960 Graphics_text (g, x + 0.5 * width, y + descent, markString.string);
961 Graphics_setColour (g, colour);
962 if (constraint -> tiedToTheLeft)
963 Graphics_setLineType (g, Graphics_DOTTED);
964 Graphics_line (g, x, y, x, y + rowHeight);
965 Graphics_setLineType (g, Graphics_DRAWN);
966 Graphics_line (g, x, y + rowHeight, x + width, y + rowHeight);
967 x += width;
968 }
969 /*
970 Draw harmony.
971 */
972 if (my decisionStrategy != kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
973 Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
974 const double value = tableau -> candidates [icand]. harmony;
975 if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
976 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
977 {
978 Graphics_text (g, x, y + descent, Melder_float (Melder_half (value)));
979 } else {
980 Graphics_text (g, x, y + descent, Melder_fixed (value, 3));
981 }
982 }
983 }
984 /*
985 Draw box.
986 */
987 x = doubleLineDx; // left side of tableau
988 y = 1.0 - doubleLineDy;
989 Graphics_rectangle (g, x, x + tableauWidth,
990 y - headerHeight - tableau -> numberOfCandidates * rowHeight - doubleLineDy, y);
991 } catch (MelderError) {
992 Melder_throw (me, U": tableau not drawn.");
993 }
994 }
995
OTGrammar_generateInputs(OTGrammar me,integer numberOfTrials)996 autoStrings OTGrammar_generateInputs (OTGrammar me, integer numberOfTrials) {
997 try {
998 autoStrings thee = Thing_new (Strings);
999 thy strings = autoSTRVEC (thy numberOfStrings = numberOfTrials);
1000 for (integer i = 1; i <= numberOfTrials; i ++) {
1001 integer itab = NUMrandomInteger (1, my numberOfTableaus);
1002 thy strings [i] = Melder_dup (my tableaus [itab]. input.get());
1003 }
1004 return thee;
1005 } catch (MelderError) {
1006 Melder_throw (me, U": inputs not generated.");
1007 }
1008 }
1009
OTGrammar_getInputs(OTGrammar me)1010 autoStrings OTGrammar_getInputs (OTGrammar me) {
1011 try {
1012 autoStrings thee = Thing_new (Strings);
1013 thy strings = autoSTRVEC (thy numberOfStrings = my numberOfTableaus);
1014 for (integer i = 1; i <= my numberOfTableaus; i ++)
1015 thy strings [i] = Melder_dup (my tableaus [i]. input.get());
1016 return thee;
1017 } catch (MelderError) {
1018 Melder_throw (me, U": inputs not gotten.");
1019 }
1020 }
1021
OTGrammar_inputToOutput(OTGrammar me,conststring32 input,double evaluationNoise)1022 autostring32 OTGrammar_inputToOutput (OTGrammar me, conststring32 input, double evaluationNoise) {
1023 try {
1024 OTGrammar_newDisharmonies (me, evaluationNoise);
1025 integer itab = OTGrammar_getTableau (me, input);
1026 integer winner = OTGrammar_getWinner (me, itab);
1027 if (winner == 0)
1028 Melder_throw (U"No winner");
1029 return Melder_dup (my tableaus [itab]. candidates [winner]. output.get());
1030 } catch (MelderError) {
1031 Melder_throw (me, U": output not computed from input \"", input, U"\".");
1032 }
1033 }
1034
OTGrammar_inputsToOutputs(OTGrammar me,Strings inputs,double evaluationNoise)1035 autoStrings OTGrammar_inputsToOutputs (OTGrammar me, Strings inputs, double evaluationNoise) {
1036 try {
1037 autoStrings him = Thing_new (Strings);
1038 integer n = inputs -> numberOfStrings;
1039 his numberOfStrings = n;
1040 his strings = autoSTRVEC (n);
1041 for (integer i = 1; i <= n; i ++)
1042 his strings [i] = OTGrammar_inputToOutput (me, inputs -> strings [i].get(), evaluationNoise);
1043 return him;
1044 } catch (MelderError) {
1045 Melder_throw (me, U": outputs not computed.");
1046 }
1047 }
1048
OTGrammar_inputToOutputs(OTGrammar me,conststring32 input,integer n,double evaluationNoise)1049 autoStrings OTGrammar_inputToOutputs (OTGrammar me, conststring32 input, integer n, double evaluationNoise) {
1050 try {
1051 autoStrings thee = Thing_new (Strings);
1052 thy numberOfStrings = n;
1053 thy strings = autoSTRVEC (n);
1054 for (integer i = 1; i <= n; i ++)
1055 thy strings [i] = OTGrammar_inputToOutput (me, input, evaluationNoise);
1056 return thee;
1057 } catch (MelderError) {
1058 Melder_throw (me, U": output not computed.");
1059 }
1060 }
1061
OTGrammar_to_Distribution(OTGrammar me,integer trialsPerInput,double noise)1062 autoDistributions OTGrammar_to_Distribution (OTGrammar me, integer trialsPerInput, double noise) {
1063 try {
1064 integer totalNumberOfOutputs = 0, nout = 0;
1065 /*
1066 Count the total number of outputs.
1067 */
1068 for (integer itab = 1; itab <= my numberOfTableaus; itab ++)
1069 totalNumberOfOutputs += my tableaus [itab]. numberOfCandidates;
1070 /*
1071 Create the distribution. One row for every output form.
1072 */
1073 autoDistributions thee = Distributions_create (totalNumberOfOutputs, 1);
1074 /*
1075 Measure every input form.
1076 */
1077 autoMelderProgress progress (U"OTGrammar: compute output distribution.");
1078 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
1079 OTGrammarTableau tableau = & my tableaus [itab];
1080 Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
1081 /*
1082 Set the row labels to the output strings.
1083 */
1084 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
1085 thy rowLabels [nout + icand] = Melder_dup (
1086 Melder_cat (tableau -> input.get(), U" \\-> ", tableau -> candidates [icand]. output.get())
1087 );
1088 }
1089 /*
1090 Compute a number of outputs and store the results.
1091 */
1092 for (integer itrial = 1; itrial <= trialsPerInput; itrial ++) {
1093 OTGrammar_newDisharmonies (me, noise);
1094 integer iwinner = OTGrammar_getWinner (me, itab);
1095 thy data [nout + iwinner] [1] += 1;
1096 }
1097 /*
1098 Update the offset.
1099 */
1100 nout += tableau -> numberOfCandidates;
1101 }
1102 return thee;
1103 } catch (MelderError) {
1104 Melder_throw (me, U": output distribution not computed.");
1105 }
1106 }
1107
OTGrammar_to_PairDistribution(OTGrammar me,integer trialsPerInput,double noise)1108 autoPairDistribution OTGrammar_to_PairDistribution (OTGrammar me, integer trialsPerInput, double noise) {
1109 try {
1110 integer nout = 0;
1111 /*
1112 Create the distribution. One row for every output form.
1113 */
1114 autoPairDistribution thee = PairDistribution_create ();
1115 /*
1116 Measure every input form.
1117 */
1118 autoMelderProgress progress (U"OTGrammar: compute output distribution.");
1119 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
1120 OTGrammarTableau tableau = & my tableaus [itab];
1121 Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
1122 /*
1123 Copy the input and output strings to the target object.
1124 */
1125 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
1126 PairDistribution_add (thee.get(), tableau -> input.get(), tableau -> candidates [icand]. output.get(), 0.0);
1127 }
1128 /*
1129 Compute a number of outputs and store the results.
1130 */
1131 for (integer itrial = 1; itrial <= trialsPerInput; itrial ++) {
1132 OTGrammar_newDisharmonies (me, noise);
1133 integer iwinner = OTGrammar_getWinner (me, itab);
1134 thy pairs.at [nout + iwinner] -> weight += 1.0;
1135 }
1136 /*
1137 Update the offset.
1138 */
1139 nout += tableau -> numberOfCandidates;
1140 }
1141 return thee;
1142 } catch (MelderError) {
1143 Melder_throw (me, U": output distribution not computed.");
1144 }
1145 }
1146
honoursFixedRankings(OTGrammar me)1147 static bool honoursFixedRankings (OTGrammar me) {
1148 for (integer i = 1; i <= my numberOfFixedRankings; i ++) {
1149 integer higher = my fixedRankings [i]. higher, lower = my fixedRankings [i]. lower;
1150 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1151 if (my index [icons] == higher)
1152 break; // detected higher before lower: OK
1153 if (my index [icons] == lower)
1154 return false;
1155 }
1156 }
1157 return true;
1158 }
1159
OTGrammar_measureTypology_WEAK(OTGrammar me)1160 autoDistributions OTGrammar_measureTypology_WEAK (OTGrammar me) {
1161 try {
1162 integer totalNumberOfOutputs = 0, nout = 0, nperm, factorial [1+12];
1163 if (my numberOfConstraints > 12)
1164 Melder_throw (U"Cannot handle more than 12 constraints.");
1165 factorial [0] = 1;
1166 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1167 factorial [icons] = factorial [icons - 1] * icons;
1168 nperm = factorial [my numberOfConstraints];
1169 /*
1170 Count the total number of outputs.
1171 */
1172 for (integer itab = 1; itab <= my numberOfTableaus; itab ++)
1173 totalNumberOfOutputs += my tableaus [itab]. numberOfCandidates;
1174 /*
1175 Create the distribution. One row for every output form.
1176 */
1177 autoDistributions thee = Distributions_create (totalNumberOfOutputs, 1);
1178 /*
1179 Measure every input form.
1180 */
1181 autoMelderProgress progress (U"Measuring typology...");
1182 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
1183 OTGrammarTableau tableau = & my tableaus [itab];
1184 Melder_progress ((itab - 0.5) / my numberOfTableaus, U"Measuring input \"", tableau -> input.get(), U"\"");
1185 /*
1186 Set the row labels to the output strings.
1187 */
1188 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
1189 thy rowLabels [nout + icand] = Melder_dup (
1190 Melder_cat (tableau -> input.get(), U" \\-> ", tableau -> candidates [icand]. output.get())
1191 );
1192 }
1193 /*
1194 Compute a number of outputs and store the results.
1195 */
1196 for (integer iperm = 0; iperm < nperm; iperm ++) {
1197 integer permleft = iperm, iwinner;
1198 /* Initialize to 12345 before permuting. */
1199 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1200 my index [icons] = icons;
1201 for (integer icons = 1; icons < my numberOfConstraints; icons ++) {
1202 integer fac = factorial [my numberOfConstraints - icons], shift = permleft / fac, dummy;
1203 /*
1204 Swap constraint with the one at a distance 'shift'.
1205 */
1206 dummy = my index [icons];
1207 my index [icons] = my index [icons + shift];
1208 my index [icons + shift] = dummy;
1209 permleft %= fac;
1210 }
1211 if (honoursFixedRankings (me)) {
1212 iwinner = OTGrammar_getWinner (me, itab);
1213 thy data [nout + iwinner] [1] += 1;
1214 }
1215 }
1216 /*
1217 Update the offset.
1218 */
1219 nout += tableau -> numberOfCandidates;
1220 }
1221 return thee;
1222 } catch (MelderError) {
1223 Melder_throw (me, U": typology not measured.");
1224 }
1225 }
1226
learningStep(double mean,double relativeSpreading)1227 static double learningStep (double mean, double relativeSpreading) {
1228 return relativeSpreading == 0.0 ? mean : NUMrandomGauss (mean, relativeSpreading * mean);
1229 }
1230
OTGrammar_honourLocalRankings(OTGrammar me,double plasticity,double relativePlasticityNoise,bool * grammarHasChanged)1231 static void OTGrammar_honourLocalRankings (OTGrammar me, double plasticity, double relativePlasticityNoise, bool *grammarHasChanged) {
1232 bool improved;
1233 do {
1234 improved = false;
1235 for (integer irank = 1; irank <= my numberOfFixedRankings; irank ++) {
1236 OTGrammarFixedRanking fixedRanking = & my fixedRankings [irank];
1237 OTGrammarConstraint higher = & my constraints [fixedRanking -> higher], lower = & my constraints [fixedRanking -> lower];
1238 while (higher -> ranking <= lower -> ranking) {
1239 lower -> ranking -= learningStep (plasticity, relativePlasticityNoise);
1240 if (grammarHasChanged)
1241 *grammarHasChanged = true;
1242 improved = true;
1243 }
1244 }
1245 } while (improved);
1246 }
1247
OTGrammar_modifyRankings(OTGrammar me,integer itab,integer iwinner,integer iadult,kOTGrammar_rerankingStrategy updateRule,bool honourLocalRankings,double plasticity,double relativePlasticityNoise,bool warnIfStalled,bool * out_grammarHasChanged)1248 static void OTGrammar_modifyRankings (OTGrammar me, integer itab, integer iwinner, integer iadult,
1249 kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
1250 double plasticity, double relativePlasticityNoise, bool warnIfStalled, bool *out_grammarHasChanged)
1251 {
1252 try {
1253 OTGrammarTableau tableau = & my tableaus [itab];
1254 OTGrammarCandidate winner = & tableau -> candidates [iwinner], adult = & tableau -> candidates [iadult];
1255 double step = learningStep (plasticity, relativePlasticityNoise);
1256 bool multiplyStepByNumberOfViolations =
1257 my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
1258 my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT ||
1259 my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
1260 my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG ||
1261 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
1262 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY;
1263 if (Melder_debug != 0) {
1264 /*
1265 * Perhaps override the standard update rule.
1266 */
1267 if (Melder_debug == 26)
1268 multiplyStepByNumberOfViolations = false; // OT-GLA
1269 else if (Melder_debug == 27)
1270 multiplyStepByNumberOfViolations = true; // HG-GLA
1271 }
1272 if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ONE) {
1273 const integer icons = NUMrandomInteger (1, my numberOfConstraints);
1274 const OTGrammarConstraint constraint = & my constraints [icons];
1275 double constraintStep = step * constraint -> plasticity;
1276 const integer winnerMarks = winner -> marks [icons];
1277 const integer adultMarks = adult -> marks [icons];
1278 if (adultMarks > winnerMarks) {
1279 if (multiplyStepByNumberOfViolations)
1280 constraintStep *= adultMarks - winnerMarks;
1281 constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
1282 if (out_grammarHasChanged)
1283 *out_grammarHasChanged = true;
1284 }
1285 if (winnerMarks > adultMarks) {
1286 if (multiplyStepByNumberOfViolations)
1287 constraintStep *= winnerMarks - adultMarks;
1288 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
1289 if (out_grammarHasChanged)
1290 *out_grammarHasChanged = true;
1291 }
1292 } else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL) {
1293 bool changed = false;
1294 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1295 const OTGrammarConstraint constraint = & my constraints [icons];
1296 double constraintStep = step * constraint -> plasticity;
1297 const integer winnerMarks = winner -> marks [icons];
1298 const integer adultMarks = adult -> marks [icons];
1299 if (adultMarks > winnerMarks) {
1300 if (multiplyStepByNumberOfViolations)
1301 constraintStep *= adultMarks - winnerMarks;
1302 constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
1303 changed = true;
1304 }
1305 if (winnerMarks > adultMarks) {
1306 if (multiplyStepByNumberOfViolations)
1307 constraintStep *= winnerMarks - adultMarks;
1308 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
1309 changed = true;
1310 }
1311 }
1312 if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG)
1313 {
1314 longdouble sumOfWeights = 0.0;
1315 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1316 sumOfWeights += my constraints [icons]. ranking;
1317 const double averageWeight = (double) sumOfWeights / my numberOfConstraints;
1318 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1319 my constraints [icons]. ranking -= averageWeight;
1320 }
1321 if (out_grammarHasChanged)
1322 *out_grammarHasChanged = changed;
1323 } else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL_SKIPPABLE) {
1324 bool changed = false;
1325 integer winningConstraints = 0, adultConstraints = 0;
1326 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1327 const integer winnerMarks = winner -> marks [icons];
1328 const integer adultMarks = adult -> marks [icons];
1329 if (adultMarks > winnerMarks)
1330 adultConstraints ++;
1331 if (winnerMarks > adultMarks)
1332 winningConstraints ++;
1333 }
1334 if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1335 const OTGrammarConstraint constraint = & my constraints [icons];
1336 double constraintStep = step * constraint -> plasticity;
1337 const integer winnerMarks = winner -> marks [icons];
1338 const integer adultMarks = adult -> marks [icons];
1339 if (adultMarks > winnerMarks) {
1340 if (multiplyStepByNumberOfViolations)
1341 constraintStep *= adultMarks - winnerMarks;
1342 constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
1343 changed = true;
1344 }
1345 if (winnerMarks > adultMarks) {
1346 if (multiplyStepByNumberOfViolations)
1347 constraintStep *= winnerMarks - adultMarks;
1348 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
1349 changed = true;
1350 }
1351 }
1352 if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG) {
1353 longdouble sumOfWeights = 0.0;
1354 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1355 sumOfWeights += my constraints [icons]. ranking;
1356 const double averageWeight = (double) sumOfWeights / my numberOfConstraints;
1357 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1358 my constraints [icons]. ranking -= averageWeight;
1359 }
1360 if (out_grammarHasChanged)
1361 *out_grammarHasChanged = changed;
1362 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_UNCANCELLED) {
1363 integer winningConstraints = 0, adultConstraints = 0;
1364 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1365 const integer winnerMarks = winner -> marks [icons];
1366 const integer adultMarks = adult -> marks [icons];
1367 if (adultMarks > winnerMarks)
1368 adultConstraints ++;
1369 if (winnerMarks > adultMarks)
1370 winningConstraints ++;
1371 }
1372 if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1373 const OTGrammarConstraint constraint = & my constraints [icons];
1374 double constraintStep = step * constraint -> plasticity;
1375 const integer winnerMarks = winner -> marks [icons];
1376 const integer adultMarks = adult -> marks [icons];
1377 if (adultMarks > winnerMarks) {
1378 if (multiplyStepByNumberOfViolations)
1379 constraintStep *= adultMarks - winnerMarks;
1380 constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
1381 //constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) * winningConstraints;
1382 if (out_grammarHasChanged)
1383 *out_grammarHasChanged = true;
1384 }
1385 if (winnerMarks > adultMarks) {
1386 if (multiplyStepByNumberOfViolations)
1387 constraintStep *= winnerMarks - adultMarks;
1388 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
1389 //constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * adultConstraints;
1390 if (out_grammarHasChanged)
1391 *out_grammarHasChanged = true;
1392 }
1393 }
1394 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL) {
1395 integer winningConstraints = 0, adultConstraints = 0;
1396 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1397 const integer winnerMarks = winner -> marks [icons];
1398 const integer adultMarks = adult -> marks [icons];
1399 if (adultMarks > 0)
1400 adultConstraints ++;
1401 if (winnerMarks > 0)
1402 winningConstraints ++;
1403 }
1404 if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1405 const OTGrammarConstraint constraint = & my constraints [icons];
1406 double constraintStep = step * constraint -> plasticity;
1407 const integer winnerMarks = winner -> marks [icons];
1408 const integer adultMarks = adult -> marks [icons];
1409 if (adultMarks > 0) {
1410 if (multiplyStepByNumberOfViolations)
1411 constraintStep *= adultMarks /*- winnerMarks*/;
1412 constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / adultConstraints;
1413 if (out_grammarHasChanged)
1414 *out_grammarHasChanged = true;
1415 }
1416 if (winnerMarks > 0) {
1417 if (multiplyStepByNumberOfViolations)
1418 constraintStep *= winnerMarks /*- adultMarks*/;
1419 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
1420 if (out_grammarHasChanged)
1421 *out_grammarHasChanged = true;
1422 }
1423 }
1424 } else if (updateRule == kOTGrammar_rerankingStrategy::EDCD || updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
1425 /*
1426 Determine the crucial winner mark.
1427 */
1428 double pivotRanking;
1429 bool equivalent = true;
1430 integer icons = 1;
1431 for (; icons <= my numberOfConstraints; icons ++) {
1432 const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
1433 const integer adultMarks = adult -> marks [my index [icons]];
1434 if (adultMarks < winnerMarks)
1435 break;
1436 if (adultMarks > winnerMarks)
1437 equivalent = false;
1438 }
1439 if (icons > my numberOfConstraints) { // completed the loop?
1440 if (warnIfStalled && ! equivalent)
1441 Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
1442 U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
1443 return; // Tesar & Smolensky (2000: 67): "stopped dead in its tracks"
1444 }
1445 /*
1446 Determine the stratum into which some constraints will be demoted.
1447 */
1448 pivotRanking = my constraints [my index [icons]]. ranking;
1449 if (updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
1450 integer numberOfConstraintsToDemote = 0;
1451 for (icons = 1; icons <= my numberOfConstraints; icons ++) {
1452 const integer winnerMarks = winner -> marks [icons];
1453 const integer adultMarks = adult -> marks [icons];
1454 if (adultMarks > winnerMarks) {
1455 const OTGrammarConstraint constraint = & my constraints [icons];
1456 if (constraint -> ranking >= pivotRanking)
1457 numberOfConstraintsToDemote += 1;
1458 }
1459 }
1460 if (numberOfConstraintsToDemote > 0) {
1461 for (icons = 1; icons <= my numberOfConstraints; icons ++) {
1462 const OTGrammarConstraint constraint = & my constraints [icons];
1463 if (constraint -> ranking < pivotRanking) {
1464 constraint -> ranking -= numberOfConstraintsToDemote * step * constraint -> plasticity;
1465 if (out_grammarHasChanged)
1466 *out_grammarHasChanged = true;
1467 }
1468 }
1469 }
1470 }
1471 /*
1472 Demote all the uniquely violated constraints in the adult form
1473 that have rankings not lower than the pivot.
1474 */
1475 for (icons = 1; icons <= my numberOfConstraints; icons ++) {
1476 integer numberOfConstraintsDemoted = 0;
1477 const integer winnerMarks = winner -> marks [my index [icons]]; // for the vacation version, the order is important, therefore indirect
1478 const integer adultMarks = adult -> marks [my index [icons]];
1479 if (adultMarks > winnerMarks) {
1480 const OTGrammarConstraint constraint = & my constraints [my index [icons]];
1481 const double constraintStep = step * constraint -> plasticity;
1482 if (constraint -> ranking >= pivotRanking) {
1483 numberOfConstraintsDemoted += 1;
1484 constraint -> ranking = pivotRanking - numberOfConstraintsDemoted * constraintStep; // this preserves the order of the demotees
1485 if (out_grammarHasChanged)
1486 *out_grammarHasChanged = true;
1487 }
1488 }
1489 }
1490 } else if (updateRule == kOTGrammar_rerankingStrategy::DEMOTION_ONLY) {
1491 /*
1492 Determine the crucial adult mark.
1493 */
1494 integer crucialAdultMark;
1495 OTGrammarConstraint offendingConstraint;
1496 integer icons = 1;
1497 for (; icons <= my numberOfConstraints; icons ++) {
1498 const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, so we indirect
1499 const integer adultMarks = adult -> marks [my index [icons]];
1500 if (my constraints [my index [icons]]. tiedToTheRight)
1501 Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
1502 if (adultMarks < winnerMarks)
1503 Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
1504 if (adultMarks > winnerMarks)
1505 break;
1506 }
1507 if (icons > my numberOfConstraints) // completed the loop?
1508 Melder_throw (U"Adult form equals correct candidate.");
1509 crucialAdultMark = icons;
1510 /*
1511 Demote the highest uniquely violated constraint in the adult form.
1512 */
1513 offendingConstraint = & my constraints [my index [crucialAdultMark]];
1514 double constraintStep = step * offendingConstraint -> plasticity;
1515 offendingConstraint -> ranking -= constraintStep;
1516 if (out_grammarHasChanged)
1517 *out_grammarHasChanged = true;
1518 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN) {
1519 integer numberOfUp = 0;
1520 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1521 const integer winnerMarks = winner -> marks [icons];
1522 const integer adultMarks = adult -> marks [icons];
1523 if (winnerMarks > adultMarks)
1524 numberOfUp ++;
1525 }
1526 if (numberOfUp > 0) {
1527 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1528 const OTGrammarConstraint constraint = & my constraints [icons];
1529 double constraintStep = step * constraint -> plasticity;
1530 const integer winnerMarks = winner -> marks [icons];
1531 const integer adultMarks = adult -> marks [icons];
1532 if (winnerMarks > adultMarks) {
1533 if (multiplyStepByNumberOfViolations)
1534 constraintStep *= winnerMarks - adultMarks;
1535 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / numberOfUp;
1536 if (out_grammarHasChanged)
1537 *out_grammarHasChanged = true;
1538 }
1539 }
1540 integer winnerMarks = 0, adultMarks = 0;
1541 integer icons = 1;
1542 for (; icons <= my numberOfConstraints; icons ++) {
1543 winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
1544 adultMarks = adult -> marks [my index [icons]];
1545 if (my constraints [my index [icons]]. tiedToTheRight)
1546 Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
1547 if (adultMarks < winnerMarks)
1548 Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
1549 if (adultMarks > winnerMarks) break;
1550 }
1551 if (icons > my numberOfConstraints) // completed the loop?
1552 Melder_throw (U"Adult form equals correct candidate.");
1553 const integer crucialAdultMark = icons;
1554 /*
1555 Demote the highest uniquely violated constraint in the adult form.
1556 */
1557 const OTGrammarConstraint offendingConstraint = & my constraints [my index [crucialAdultMark]];
1558 double constraintStep = step * offendingConstraint -> plasticity;
1559 if (multiplyStepByNumberOfViolations)
1560 constraintStep *= winnerMarks - adultMarks;
1561 offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
1562 if (out_grammarHasChanged)
1563 *out_grammarHasChanged = true;
1564 }
1565 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN_2012) {
1566 integer numberOfUp = 0;
1567 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1568 const integer winnerMarks = winner -> marks [icons];
1569 const integer adultMarks = adult -> marks [icons];
1570 if (winnerMarks > adultMarks)
1571 numberOfUp ++;
1572 }
1573 if (numberOfUp > 0) {
1574 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1575 const OTGrammarConstraint constraint = & my constraints [icons];
1576 double constraintStep = step * constraint -> plasticity;
1577 const integer winnerMarks = winner -> marks [icons];
1578 const integer adultMarks = adult -> marks [icons];
1579 if (winnerMarks > adultMarks) {
1580 if (multiplyStepByNumberOfViolations)
1581 constraintStep *= winnerMarks - adultMarks;
1582 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / (numberOfUp + 1);
1583 if (out_grammarHasChanged)
1584 *out_grammarHasChanged = true;
1585 }
1586 }
1587 integer winnerMarks = 0, adultMarks = 0;
1588 integer icons = 1;
1589 for (; icons <= my numberOfConstraints; icons ++) {
1590 winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
1591 adultMarks = adult -> marks [my index [icons]];
1592 if (my constraints [my index [icons]]. tiedToTheRight)
1593 Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
1594 if (adultMarks < winnerMarks)
1595 Melder_throw (U"Demotion-only learning step: Adult form wins! Should never happen.");
1596 if (adultMarks > winnerMarks) break;
1597 }
1598 if (icons > my numberOfConstraints) // completed the loop?
1599 Melder_throw (U"Adult form equals correct candidate.");
1600 const integer crucialAdultMark = icons;
1601 /*
1602 Demote the highest uniquely violated constraint in the adult form.
1603 */
1604 const OTGrammarConstraint offendingConstraint = & my constraints [my index [crucialAdultMark]];
1605 double constraintStep = step * offendingConstraint -> plasticity;
1606 if (multiplyStepByNumberOfViolations)
1607 constraintStep *= winnerMarks - adultMarks;
1608 offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
1609 if (out_grammarHasChanged)
1610 *out_grammarHasChanged = true;
1611 }
1612 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN) {
1613 integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
1614 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1615 const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
1616 const integer adultMarks = adult -> marks [my index [icons]];
1617 if (adultMarks < winnerMarks) {
1618 numberOfUp ++;
1619 } else if (adultMarks > winnerMarks) {
1620 if (numberOfUp == 0) {
1621 numberOfDown ++;
1622 lowestDemotableConstraint = icons;
1623 }
1624 }
1625 }
1626 if (warnIfStalled && numberOfDown == 0) {
1627 Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
1628 U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
1629 return;
1630 }
1631 if (numberOfUp > 0) {
1632 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1633 const integer constraintIndex = my index [icons];
1634 if (my constraints [constraintIndex]. tiedToTheRight)
1635 Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
1636 const OTGrammarConstraint constraint = & my constraints [constraintIndex];
1637 double constraintStep = step * constraint -> plasticity;
1638 const integer winnerMarks = winner -> marks [constraintIndex]; // the order is important, therefore indirect
1639 const integer adultMarks = adult -> marks [constraintIndex];
1640 if (adultMarks < winnerMarks) {
1641 if (multiplyStepByNumberOfViolations)
1642 constraintStep *= winnerMarks - adultMarks;
1643 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 0.0);
1644 } else if (adultMarks > winnerMarks) {
1645 if (icons <= lowestDemotableConstraint) {
1646 if (multiplyStepByNumberOfViolations)
1647 constraintStep *= adultMarks - winnerMarks;
1648 constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
1649 }
1650 }
1651 }
1652 if (out_grammarHasChanged) *out_grammarHasChanged = true;
1653 }
1654 } else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN_2012) {
1655 integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
1656 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1657 const integer winnerMarks = winner -> marks [my index [icons]]; // the order is important, therefore indirect
1658 const integer adultMarks = adult -> marks [my index [icons]];
1659 if (adultMarks < winnerMarks) {
1660 numberOfUp ++;
1661 } else if (adultMarks > winnerMarks) {
1662 if (numberOfUp == 0) {
1663 numberOfDown ++;
1664 lowestDemotableConstraint = icons;
1665 }
1666 }
1667 }
1668 if (warnIfStalled && numberOfDown == 0) {
1669 Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
1670 U"Input: ", tableau -> input.get(), U"\nCorrect output: ", adult -> output.get(), U"\nLearner's output: ", winner -> output.get());
1671 return;
1672 }
1673 if (numberOfUp > 0) {
1674 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1675 const integer constraintIndex = my index [icons];
1676 if (my constraints [constraintIndex]. tiedToTheRight)
1677 Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
1678 const OTGrammarConstraint constraint = & my constraints [constraintIndex];
1679 double constraintStep = step * constraint -> plasticity;
1680 const integer winnerMarks = winner -> marks [constraintIndex]; // the order is important, therefore indirect
1681 const integer adultMarks = adult -> marks [constraintIndex];
1682 if (adultMarks < winnerMarks) {
1683 if (multiplyStepByNumberOfViolations)
1684 constraintStep *= winnerMarks - adultMarks;
1685 constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 1.0);
1686 } else if (adultMarks > winnerMarks) {
1687 if (icons <= lowestDemotableConstraint) {
1688 if (multiplyStepByNumberOfViolations)
1689 constraintStep *= adultMarks - winnerMarks;
1690 constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
1691 }
1692 }
1693 }
1694 if (out_grammarHasChanged)
1695 *out_grammarHasChanged = true;
1696 }
1697 }
1698 if (honourLocalRankings && my numberOfFixedRankings)
1699 OTGrammar_honourLocalRankings (me, plasticity, relativePlasticityNoise, out_grammarHasChanged);
1700 } catch (MelderError) {
1701 Melder_throw (me, U": rankings not modified.");
1702 }
1703 }
1704
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)1705 void OTGrammar_learnOne (OTGrammar me, conststring32 input, conststring32 adultOutput,
1706 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
1707 double plasticity, double relativePlasticityNoise, bool newDisharmonies, bool warnIfStalled, bool *out_grammarHasChanged)
1708 {
1709 try {
1710 if (newDisharmonies)
1711 OTGrammar_newDisharmonies (me, evaluationNoise);
1712 if (out_grammarHasChanged)
1713 *out_grammarHasChanged = false;
1714
1715 /*
1716 Evaluate the input in the learner's hypothesis.
1717 */
1718 integer itab = OTGrammar_getTableau (me, input);
1719 OTGrammarTableau tableau = & my tableaus [itab];
1720
1721 /*
1722 Determine the "winner", i.e. the candidate that wins in the learner's grammar
1723 (Tesar & Smolensky call this the "loser").
1724 */
1725 integer iwinner = OTGrammar_getWinner (me, itab);
1726 OTGrammarCandidate winner = & tableau -> candidates [iwinner];
1727
1728 /*
1729 Error-driven: compare the adult winner (the correct candidate) and the learner's winner.
1730 */
1731 if (str32equ (winner -> output.get(), adultOutput)) return; // as far as we know, the grammar is already correct: don't update rankings
1732
1733 /*
1734 Find (perhaps the learner's interpretation of) the adult output in the learner's own tableau
1735 (Tesar & Smolensky call this the "winner").
1736 */
1737 integer iadult = 1;
1738 for (; iadult <= tableau -> numberOfCandidates; iadult ++) {
1739 OTGrammarCandidate cand = & tableau -> candidates [iadult];
1740 if (str32equ (cand -> output.get(), adultOutput))
1741 break;
1742 }
1743 if (iadult > tableau -> numberOfCandidates)
1744 Melder_throw (U"Cannot generate adult output \"", adultOutput, U"\".");
1745
1746 /*
1747 Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
1748 The grammar will have to change.
1749 */
1750 OTGrammar_modifyRankings (me, itab, iwinner, iadult, updateRule, honourLocalRankings,
1751 plasticity, relativePlasticityNoise, warnIfStalled, out_grammarHasChanged);
1752 } catch (MelderError) {
1753 Melder_throw (me, U": not learned from input \"", input, U"\" and adult output \"", adultOutput, U"\".");
1754 }
1755 }
1756
OTGrammar_learn(OTGrammar me,Strings inputs,Strings outputs,double evaluationNoise,enum kOTGrammar_rerankingStrategy updateRule,bool honourLocalRankings,double plasticity,double relativePlasticityNoise,integer numberOfChews)1757 void OTGrammar_learn (OTGrammar me, Strings inputs, Strings outputs,
1758 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
1759 double plasticity, double relativePlasticityNoise, integer numberOfChews)
1760 {
1761 if (! inputs)
1762 inputs = outputs;
1763 try {
1764 const integer n = inputs -> numberOfStrings;
1765 Melder_require (outputs -> numberOfStrings == n,
1766 U"Numbers of strings in input and output should be equal.");
1767 for (integer i = 1; i <= n; i ++) {
1768 for (integer ichew = 1; ichew <= numberOfChews; ichew ++)
1769 OTGrammar_learnOne (me, inputs -> strings [i].get(), outputs -> strings [i].get(),
1770 evaluationNoise, updateRule, honourLocalRankings,
1771 plasticity, relativePlasticityNoise, true, true, nullptr
1772 );
1773 }
1774 } catch (MelderError) {
1775 Melder_throw (me, U": not learned from ", inputs, U" (inputs) and ", outputs, U" (outputs).");
1776 }
1777 }
1778
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)1779 void OTGrammar_PairDistribution_learn (OTGrammar me, PairDistribution thee,
1780 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
1781 double initialPlasticity, integer replicationsPerPlasticity, double plasticityDecrement,
1782 integer numberOfPlasticities, double relativePlasticityNoise, integer numberOfChews)
1783 {
1784 integer idatum = 0, numberOfData = numberOfPlasticities * replicationsPerPlasticity;
1785 try {
1786 double plasticity = initialPlasticity;
1787 autoMelderMonitor monitor (U"Learning with full knowledge...");
1788 for (integer iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
1789 for (integer ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
1790 conststring32 input, output;
1791 PairDistribution_peekPair (thee, & input, & output);
1792 ++ idatum;
1793 if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
1794 Graphics_beginMovieFrame (monitor.graphics(), nullptr);
1795 Graphics_setWindow (monitor.graphics(), 0, numberOfData, 50, 150);
1796 for (integer icons = 1; icons <= 14 && icons <= my numberOfConstraints; icons ++) {
1797 Graphics_setGrey (monitor.graphics(), (double) icons / 14);
1798 Graphics_line (monitor.graphics(),
1799 idatum, my constraints [icons]. ranking,
1800 idatum, my constraints [icons]. ranking + 1.0
1801 );
1802 }
1803 Graphics_endMovieFrame (monitor.graphics(), 0.0);
1804 }
1805 Melder_monitor ((double) idatum / numberOfData,
1806 U"Processing input-output pair ", idatum,
1807 U" out of ", numberOfData, U": ", input, U" -> ", output
1808 );
1809 for (integer ichew = 1; ichew <= numberOfChews; ichew ++)
1810 OTGrammar_learnOne (me, input, output,
1811 evaluationNoise, updateRule, honourLocalRankings,
1812 plasticity, relativePlasticityNoise, true, true, nullptr
1813 );
1814 }
1815 plasticity *= plasticityDecrement;
1816 }
1817 } catch (MelderError) {
1818 if (idatum > 1)
1819 Melder_appendError (U"Only ", idatum - 1, U" input-output pairs out of ", numberOfData, U" were processed.");
1820 Melder_throw (me, U": did not complete learning from ", thee, U".");
1821 }
1822 }
1823
PairDistribution_getNumberOfAttestedOutputs(PairDistribution me,conststring32 input,conststring32 * out_attestedOutput)1824 static integer PairDistribution_getNumberOfAttestedOutputs (PairDistribution me, conststring32 input, conststring32 *out_attestedOutput) {
1825 integer result = 0;
1826 for (integer ipair = 1; ipair <= my pairs.size; ipair ++) {
1827 PairProbability pair = my pairs.at [ipair];
1828 if (str32equ (pair -> string1.get(), input) && pair -> weight > 0.0) {
1829 if (out_attestedOutput) *out_attestedOutput = pair -> string2.get();
1830 result ++;
1831 }
1832 }
1833 return result;
1834 }
1835
OTGrammar_PairDistribution_findPositiveWeights(OTGrammar me,PairDistribution thee,double weightFloor,double marginOfSeparation)1836 bool OTGrammar_PairDistribution_findPositiveWeights (OTGrammar me, PairDistribution thee, double weightFloor, double marginOfSeparation) {
1837 NUMlinprog linprog = nullptr;
1838 try {
1839 bool result = false;
1840 if (my decisionStrategy != kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR &&
1841 my decisionStrategy != kOTGrammar_decisionStrategy::LINEAR_OT &&
1842 my decisionStrategy != kOTGrammar_decisionStrategy::POSITIVE_HG &&
1843 my decisionStrategy != kOTGrammar_decisionStrategy::EXPONENTIAL_HG)
1844 {
1845 Melder_throw (U"To find positive weights, the decision strategy should be HarmonicGrammar, LinearOT, PositiveHG, or ExponentialHG.");
1846 }
1847 autoINTVEC optimalCandidates = raw_INTVEC (my numberOfTableaus);
1848 /*
1849 Check that there is exactly one optimal output for each input.
1850 */
1851 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
1852 OTGrammarTableau tab = & my tableaus [itab];
1853 conststring32 attestedOutput = nullptr;
1854 integer numberOfAttestedOutputs = PairDistribution_getNumberOfAttestedOutputs (thee, tab -> input.get(), & attestedOutput);
1855 if (numberOfAttestedOutputs == 0) {
1856 Melder_throw (U"Input \"", tab -> input.get(), U"\" has no attested output.");
1857 } else if (numberOfAttestedOutputs > 1) {
1858 Melder_throw (U"Input \"", tab -> input.get(), U"\" has more than one attested output.");
1859 } else {
1860 Melder_assert (attestedOutput);
1861 for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
1862 OTGrammarCandidate cand = & tab -> candidates [icand];
1863 if (str32equ (attestedOutput, cand -> output.get()))
1864 optimalCandidates [itab] = icand;
1865 }
1866 }
1867 Melder_assert (optimalCandidates [itab] != 0);
1868 }
1869 /*
1870 Create linear programming problem.
1871 */
1872 linprog = NUMlinprog_new (false);
1873 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1874 NUMlinprog_addVariable (linprog, weightFloor, undefined, 1.0);
1875 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
1876 OTGrammarTableau tab = & my tableaus [itab];
1877 const integer ioptimalCandidate = optimalCandidates [itab];
1878 Melder_assert (ioptimalCandidate >= 1);
1879 Melder_assert (ioptimalCandidate <= tab -> numberOfCandidates);
1880 OTGrammarCandidate optimalCandidate = & tab -> candidates [ioptimalCandidate];
1881 for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
1882 if (icand != ioptimalCandidate) {
1883 OTGrammarCandidate cand = & tab -> candidates [icand];
1884 NUMlinprog_addConstraint (linprog, marginOfSeparation, undefined);
1885 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1886 NUMlinprog_addConstraintCoefficient (linprog, cand -> marks [icons] - optimalCandidate -> marks [icons]);
1887 }
1888 }
1889 }
1890 NUMlinprog_run (linprog);
1891 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1892 const double weighting = NUMlinprog_getPrimalValue (linprog, icons);
1893 Melder_assert (weighting >= weightFloor);
1894 my constraints [icons]. ranking = my constraints [icons]. disharmony =
1895 my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ? log (weighting) : weighting;
1896 }
1897 NUMlinprog_delete (linprog);
1898 return result;
1899 } catch (MelderError) {
1900 NUMlinprog_delete (linprog);
1901 Melder_throw (me, U": positive weights not found.");
1902 }
1903 }
1904
OTGrammar_reset(OTGrammar me,double ranking)1905 void OTGrammar_reset (OTGrammar me, double ranking) {
1906 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1907 OTGrammarConstraint constraint = & my constraints [icons];
1908 constraint -> disharmony = constraint -> ranking = ranking;
1909 }
1910 OTGrammar_sort (me);
1911 }
1912
OTGrammar_resetToRandomRanking(OTGrammar me,double mean,double standardDeviation)1913 void OTGrammar_resetToRandomRanking (OTGrammar me, double mean, double standardDeviation) {
1914 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1915 OTGrammarConstraint constraint = & my constraints [my index [icons]];
1916 constraint -> disharmony = constraint -> ranking = NUMrandomGauss (mean, standardDeviation);
1917 }
1918 OTGrammar_sort (me);
1919 }
1920
OTGrammar_resetToRandomTotalRanking(OTGrammar me,double maximumRanking,double rankingDistance)1921 void OTGrammar_resetToRandomTotalRanking (OTGrammar me, double maximumRanking, double rankingDistance) {
1922 /*
1923 First put the constraints in a random order and build a random index.
1924 */
1925 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1926 OTGrammarConstraint constraint = & my constraints [icons];
1927 constraint -> ranking = 0.0;
1928 }
1929 OTGrammar_newDisharmonies (me, 1.0);
1930 /*
1931 Then use the random index to yield a cascade of rankings.
1932 */
1933 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1934 OTGrammarConstraint constraint = & my constraints [my index [icons]];
1935 constraint -> disharmony = constraint -> ranking = maximumRanking - (icons - 1) * rankingDistance;
1936 }
1937 OTGrammar_sort (me);
1938 }
1939
OTGrammar_setRanking(OTGrammar me,integer constraint,double ranking,double disharmony)1940 void OTGrammar_setRanking (OTGrammar me, integer constraint, double ranking, double disharmony) {
1941 try {
1942 Melder_require (constraint > 0 && constraint <= my numberOfConstraints,
1943 U"There is no constraint with number ", constraint, U".");
1944 my constraints [constraint]. ranking = ranking;
1945 my constraints [constraint]. disharmony = disharmony;
1946 OTGrammar_sort (me);
1947 } catch (MelderError) {
1948 Melder_throw (me, U": ranking of constraint ", constraint, U" not set.");
1949 }
1950 }
1951
OTGrammar_setConstraintPlasticity(OTGrammar me,integer constraint,double plasticity)1952 void OTGrammar_setConstraintPlasticity (OTGrammar me, integer constraint, double plasticity) {
1953 try {
1954 Melder_require (constraint > 0 && constraint <= my numberOfConstraints,
1955 U"There is no constraint with number ", constraint, U".");
1956 my constraints [constraint]. plasticity = plasticity;
1957 } catch (MelderError) {
1958 Melder_throw (me, U": plasticity of constraint ", constraint, U" not set.");
1959 }
1960 }
1961
1962 integer theSaveNumberOfConstraints;
1963 autoINTVEC theSaveIndex;
1964 autoVEC theSaveRankings, theSaveDisharmonies;
1965 autoBOOLVEC theSaveTiedToTheLeft, theSaveTiedToTheRight;
OTGrammar_save(OTGrammar me)1966 static void OTGrammar_save (OTGrammar me) {
1967 if (my numberOfConstraints != theSaveNumberOfConstraints) {
1968 theSaveIndex = raw_INTVEC (my numberOfConstraints);
1969 theSaveRankings = raw_VEC (my numberOfConstraints);
1970 theSaveDisharmonies = raw_VEC (my numberOfConstraints);
1971 theSaveTiedToTheLeft = raw_BOOLVEC (my numberOfConstraints);
1972 theSaveTiedToTheRight = raw_BOOLVEC (my numberOfConstraints);
1973 theSaveNumberOfConstraints = my numberOfConstraints;
1974 }
1975 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1976 theSaveIndex [icons] = my index [icons];
1977 theSaveRankings [icons] = my constraints [icons]. ranking;
1978 theSaveDisharmonies [icons] = my constraints [icons]. disharmony;
1979 theSaveTiedToTheLeft [icons] = my constraints [icons]. tiedToTheLeft;
1980 theSaveTiedToTheRight [icons] = my constraints [icons]. tiedToTheRight;
1981 }
1982 }
OTGrammar_restore(OTGrammar me)1983 static void OTGrammar_restore (OTGrammar me) {
1984 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1985 my index [icons] = theSaveIndex [icons];
1986 my constraints [icons]. ranking = theSaveRankings [icons];
1987 my constraints [icons]. disharmony = theSaveDisharmonies [icons];
1988 my constraints [icons]. tiedToTheLeft = theSaveTiedToTheLeft [icons];
1989 my constraints [icons]. tiedToTheRight = theSaveTiedToTheRight [icons];
1990 }
1991 }
1992
OTGrammar_learnOneFromPartialOutput(OTGrammar me,conststring32 partialAdultOutput,double evaluationNoise,enum kOTGrammar_rerankingStrategy updateRule,bool honourLocalRankings,double plasticity,double relativePlasticityNoise,integer numberOfChews,bool warnIfStalled)1993 void OTGrammar_learnOneFromPartialOutput (OTGrammar me, conststring32 partialAdultOutput,
1994 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
1995 double plasticity, double relativePlasticityNoise, integer numberOfChews, bool warnIfStalled)
1996 {
1997 try {
1998 OTGrammar_newDisharmonies (me, evaluationNoise);
1999 if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD)
2000 OTGrammar_save (me);
2001 integer ichew = 1;
2002 for (; ichew <= numberOfChews; ichew ++) {
2003 integer assumedAdultInputTableau, assumedAdultCandidate;
2004 OTGrammar_getInterpretiveParse (me, partialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
2005 bool grammarHasChanged = false;
2006 OTGrammar_learnOne (me,
2007 my tableaus [assumedAdultInputTableau]. input.get(),
2008 my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get(),
2009 evaluationNoise, updateRule, honourLocalRankings,
2010 plasticity, relativePlasticityNoise, Melder_debug == 47, warnIfStalled, & grammarHasChanged
2011 );
2012 if (! grammarHasChanged)
2013 return;
2014 }
2015 if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD && ichew > numberOfChews) {
2016 /*
2017 Is the partial output form grammatical by now?
2018 */
2019 integer assumedAdultInputTableau, assumedAdultCandidate;
2020 OTGrammar_getInterpretiveParse (me, partialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
2021 const integer ilearnerCandidate = OTGrammar_getWinner (me, assumedAdultInputTableau);
2022 OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [ilearnerCandidate];
2023 if (! str32equ (learnerCandidate -> output.get(),
2024 my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
2025 { /* Still ungrammatical? */
2026 /*
2027 Backtrack as in Tesar & Smolensky 2000:69.
2028 */
2029 OTGrammar_restore (me);
2030 }
2031 }
2032 } catch (MelderError) {
2033 Melder_throw (me, U": not learned from partial adult output \"", partialAdultOutput, U"\".");
2034 }
2035 }
2036
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)2037 static void OTGrammar_learnOneFromPartialOutput_opt (OTGrammar me, conststring32 partialAdultOutput, integer ipartialAdultOutput,
2038 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
2039 double plasticity, double relativePlasticityNoise, integer numberOfChews, bool warnIfStalled,
2040 bool resampleForVirtualProduction, bool compareOnlyPartialOutput, integer resampleForCorrectForm)
2041 {
2042 try {
2043 OTGrammar_newDisharmonies (me, evaluationNoise);
2044 if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD)
2045 OTGrammar_save (me);
2046 integer ichew = 1;
2047 for (; ichew <= numberOfChews; ichew ++) {
2048 integer assumedAdultInputTableau, assumedAdultCandidate;
2049 OTGrammar_getInterpretiveParse_opt (me, ipartialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
2050 OTGrammarTableau tableau = & my tableaus [assumedAdultInputTableau];
2051 OTGrammarCandidate assumedCorrect = & tableau -> candidates [assumedAdultCandidate];
2052
2053 /*
2054 Determine the "winner", i.e. the candidate that wins in the learner's grammar
2055 (Tesar & Smolensky call this the "loser").
2056 */
2057 if (resampleForVirtualProduction)
2058 OTGrammar_newDisharmonies (me, evaluationNoise);
2059 integer iwinner = OTGrammar_getWinner (me, assumedAdultInputTableau);
2060 OTGrammarCandidate winner = & tableau -> candidates [iwinner];
2061
2062 /*
2063 Error-driven: compare the adult winner (the correct candidate) and the learner's winner.
2064 */
2065 if (compareOnlyPartialOutput) {
2066 if (str32str (winner -> output.get(), partialAdultOutput))
2067 return; // as far as we know, the grammar is already correct: don't update rankings
2068 } else {
2069 if (str32equ (winner -> output.get(), assumedCorrect -> output.get()))
2070 return; // as far as we know, the grammar is already correct: don't update rankings
2071 }
2072 if (resampleForCorrectForm) {
2073 integer itry = 1;
2074 for (; itry <= resampleForCorrectForm; itry ++) {
2075 OTGrammar_newDisharmonies (me, evaluationNoise);
2076 integer iwinner2 = OTGrammar_getWinner (me, assumedAdultInputTableau);
2077 OTGrammarCandidate winner2 = & tableau -> candidates [iwinner2];
2078 if (compareOnlyPartialOutput) {
2079 if (str32str (winner2 -> output.get(), partialAdultOutput)) {
2080 assumedAdultCandidate = iwinner2;
2081 break;
2082 }
2083 } else {
2084 if (str32equ (winner2 -> output.get(), assumedCorrect -> output.get())) {
2085 assumedAdultCandidate = iwinner2;
2086 break;
2087 }
2088 }
2089 }
2090 if (itry > resampleForCorrectForm)
2091 return; // no match, so bail out
2092 }
2093
2094 /*
2095 Now we know that the current hypothesis prefers the (wrong) learner's winner over the (correct) adult output.
2096 The grammar will have to change.
2097 */
2098 bool grammarHasChanged = false;
2099 OTGrammar_modifyRankings (me, assumedAdultInputTableau, iwinner, assumedAdultCandidate, updateRule, honourLocalRankings,
2100 plasticity, relativePlasticityNoise, warnIfStalled, & grammarHasChanged);
2101 if (! grammarHasChanged)
2102 return;
2103 }
2104 if (numberOfChews > 1 && updateRule == kOTGrammar_rerankingStrategy::EDCD && ichew > numberOfChews) {
2105 /*
2106 Is the partial output form grammatical by now?
2107 */
2108 integer assumedAdultInputTableau, assumedAdultCandidate;
2109 OTGrammar_getInterpretiveParse_opt (me, ipartialAdultOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
2110 const integer ilearnerCandidate = OTGrammar_getWinner (me, assumedAdultInputTableau);
2111 OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [ilearnerCandidate];
2112 if (! str32equ (learnerCandidate -> output.get(),
2113 my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
2114 { /* Still ungrammatical? */
2115 /*
2116 Backtrack as in Tesar & Smolensky 2000:69.
2117 */
2118 OTGrammar_restore (me);
2119 }
2120 }
2121 } catch (MelderError) {
2122 Melder_throw (me, U": not learned from partial adult output ", partialAdultOutput, U".");
2123 }
2124 }
2125
OTGrammar_createHistory(OTGrammar me,integer storeHistoryEvery,integer numberOfData)2126 static autoOTHistory OTGrammar_createHistory (OTGrammar me, integer storeHistoryEvery, integer numberOfData) {
2127 try {
2128 integer numberOfSamplingPoints = numberOfData / storeHistoryEvery; // e.g. 0, 20, 40, ...
2129 autoOTHistory thee = Thing_new (OTHistory);
2130 TableOfReal_init (thee.get(), 2 + numberOfSamplingPoints * 2, 1 + my numberOfConstraints);
2131 TableOfReal_setColumnLabel (thee.get(), 1, U"Datum");
2132 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
2133 TableOfReal_setColumnLabel (thee.get(), icons + 1, my constraints [icons]. name.get());
2134 TableOfReal_setRowLabel (thee.get(), 1, U"Initial state");
2135 thy data [1] [1] = 0.0;
2136 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
2137 thy data [1] [icons + 1] = my constraints [icons]. ranking;
2138 return thee;
2139 } catch (MelderError) {
2140 Melder_throw (me, U": history not created.");
2141 }
2142 }
2143
OTGrammar_updateHistory(OTGrammar me,OTHistory thee,integer storeHistoryEvery,integer datumNumber,conststring32 input)2144 static void OTGrammar_updateHistory (OTGrammar me, OTHistory thee, integer storeHistoryEvery, integer datumNumber, conststring32 input) {
2145 try {
2146 if (datumNumber % storeHistoryEvery == 0) {
2147 integer rowNumber = 2 * datumNumber / storeHistoryEvery;
2148 TableOfReal_setRowLabel (thee, rowNumber, input);
2149 thy data [rowNumber] [1] = datumNumber;
2150 thy data [rowNumber + 1] [1] = datumNumber;
2151 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2152 thy data [rowNumber] [icons + 1] = my constraints [icons]. disharmony;
2153 thy data [rowNumber + 1] [icons + 1] = my constraints [icons]. ranking;
2154 }
2155 }
2156 } catch (MelderError) {
2157 Melder_throw (me, U": history not updated.");
2158 }
2159 }
2160
OTGrammar_finalizeHistory(OTGrammar me,OTHistory thee,integer datumNumber)2161 static void OTGrammar_finalizeHistory (OTGrammar me, OTHistory thee, integer datumNumber) {
2162 try {
2163 TableOfReal_setRowLabel (thee, thy numberOfRows, U"Final state");
2164 thy data [thy numberOfRows] [1] = datumNumber;
2165 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
2166 thy data [thy numberOfRows] [icons + 1] = my constraints [icons]. ranking;
2167 } catch (MelderError) {
2168 Melder_throw (me, U": history not finalized.");
2169 }
2170 }
2171
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)2172 void OTGrammar_learnFromPartialOutputs (OTGrammar me, Strings partialOutputs,
2173 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
2174 double plasticity, double relativePlasticityNoise, integer numberOfChews,
2175 integer storeHistoryEvery, autoOTHistory *history_out)
2176 {
2177 try {
2178 autoOTHistory history;
2179 if (storeHistoryEvery)
2180 history = OTGrammar_createHistory (me, storeHistoryEvery, partialOutputs -> numberOfStrings);
2181 try {
2182 for (integer idatum = 1; idatum <= partialOutputs -> numberOfStrings; idatum ++) {
2183 try {
2184 OTGrammar_learnOneFromPartialOutput (me, partialOutputs -> strings [idatum].get(),
2185 evaluationNoise, updateRule, honourLocalRankings,
2186 plasticity, relativePlasticityNoise, numberOfChews, false);
2187 } catch (MelderError) {
2188 if (history)
2189 OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, partialOutputs -> strings [idatum].get()); // so that we can inspect
2190 throw;
2191 }
2192 if (history)
2193 OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, partialOutputs -> strings [idatum].get());
2194 }
2195 if (history)
2196 OTGrammar_finalizeHistory (me, history.get(), partialOutputs -> numberOfStrings);
2197 *history_out = history.move();
2198 } catch (MelderError) {
2199 *history_out = history.move(); // so that we can inspect
2200 throw;
2201 }
2202 } catch (MelderError) {
2203 Melder_throw (me, U": not learned from partial outputs ", partialOutputs, U".");
2204 }
2205 }
2206
OTGrammar_opt_deleteOutputMatching(OTGrammar me)2207 static void OTGrammar_opt_deleteOutputMatching (OTGrammar me) {
2208 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2209 OTGrammarTableau tab = & my tableaus [itab];
2210 for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
2211 OTGrammarCandidate cand = & tab -> candidates [icand];
2212 cand -> numberOfPotentialPartialOutputsMatching = 0;
2213 cand -> partialOutputMatches.reset();
2214 }
2215 }
2216 }
2217
OTGrammar_Distributions_opt_createOutputMatching(OTGrammar me,Distributions thee,integer columnNumber)2218 static void OTGrammar_Distributions_opt_createOutputMatching (OTGrammar me, Distributions thee, integer columnNumber) {
2219 try {
2220 if (columnNumber > thy numberOfColumns)
2221 Melder_throw (U"No column ", columnNumber, U" in Distributions.");
2222 if (thy numberOfRows < 1)
2223 Melder_throw (U"No candidates in Distributions.");
2224 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2225 OTGrammarTableau tab = & my tableaus [itab];
2226 for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
2227 OTGrammarCandidate cand = & tab -> candidates [icand];
2228 cand -> numberOfPotentialPartialOutputsMatching = thy numberOfRows;
2229 cand -> partialOutputMatches = zero_BOOLVEC (thy numberOfRows);
2230 }
2231 }
2232 for (integer ipartialOutput = 1; ipartialOutput <= thy numberOfRows; ipartialOutput ++) {
2233 if (thy data [ipartialOutput] [columnNumber] > 0.0) {
2234 conststring32 partialOutput = thy rowLabels [ipartialOutput].get();
2235 bool foundPartialOutput = false;
2236 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2237 OTGrammarTableau tab = & my tableaus [itab];
2238 for (integer icand = 1; icand <= tab -> numberOfCandidates; icand ++) {
2239 OTGrammarCandidate cand = & tab -> candidates [icand];
2240 if (str32str (cand -> output.get(), partialOutput)) {
2241 foundPartialOutput = true;
2242 cand -> partialOutputMatches [ipartialOutput] = true;
2243 }
2244 }
2245 }
2246 if (! foundPartialOutput)
2247 Melder_throw (U"The partial output \"", partialOutput, U"\" does not match any candidate for any input form.");
2248 }
2249 }
2250 } catch (MelderError) {
2251 OTGrammar_opt_deleteOutputMatching (me);
2252 throw;
2253 }
2254 }
2255
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)2256 void OTGrammar_Distributions_learnFromPartialOutputs (OTGrammar me, Distributions thee, integer columnNumber,
2257 double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, bool honourLocalRankings,
2258 double initialPlasticity, integer replicationsPerPlasticity, double plasticityDecrement,
2259 integer numberOfPlasticities, double relativePlasticityNoise, integer numberOfChews,
2260 integer storeHistoryEvery, autoOTHistory *history_out,
2261 bool resampleForVirtualProduction, bool compareOnlyPartialOutput, integer resampleForCorrectForm)
2262 {
2263 integer idatum = 0;
2264 const integer numberOfData = numberOfPlasticities * replicationsPerPlasticity;
2265 try {
2266 autoOTHistory history;
2267 OTGrammar_Distributions_opt_createOutputMatching (me, thee, columnNumber);
2268 autoMelderMonitor monitor (U"Learning with limited knowledge...");
2269 if (storeHistoryEvery)
2270 history = OTGrammar_createHistory (me, storeHistoryEvery, numberOfData);
2271 try {
2272 double plasticity = initialPlasticity;
2273 for (integer iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
2274 for (integer ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
2275 conststring32 partialOutput;
2276 integer ipartialOutput;
2277 Distributions_peek (thee, columnNumber, & partialOutput, & ipartialOutput);
2278 ++ idatum;
2279 if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
2280 Graphics_beginMovieFrame (monitor.graphics(), nullptr);
2281 Graphics_setWindow (monitor.graphics(), 0, numberOfData, 50.0, 150.0);
2282 for (integer icons = 1; icons <= 14 && icons <= my numberOfConstraints; icons ++) {
2283 Graphics_setGrey (monitor.graphics(), (double) icons / 14);
2284 Graphics_line (monitor.graphics(),
2285 idatum, my constraints [icons]. ranking,
2286 idatum, my constraints [icons]. ranking + 10.0
2287 );
2288 }
2289 Graphics_endMovieFrame (monitor.graphics(), 0.0);
2290 }
2291 Melder_monitor ((double) idatum / numberOfData,
2292 U"Processing partial output ", idatum, U" out of ", numberOfData, U": ",
2293 thy rowLabels [ipartialOutput].get()
2294 );
2295 try {
2296 OTGrammar_learnOneFromPartialOutput_opt (me, partialOutput, ipartialOutput,
2297 evaluationNoise, updateRule, honourLocalRankings,
2298 plasticity, relativePlasticityNoise, numberOfChews, false,
2299 resampleForVirtualProduction, compareOnlyPartialOutput, resampleForCorrectForm
2300 ); // no warning if stalled: RIP form is allowed to be harmonically bounded
2301 } catch (MelderError) {
2302 if (history)
2303 OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, thy rowLabels [ipartialOutput].get());
2304 throw;
2305 }
2306 if (history)
2307 OTGrammar_updateHistory (me, history.get(), storeHistoryEvery, idatum, thy rowLabels [ipartialOutput].get());
2308 }
2309 plasticity *= plasticityDecrement;
2310 }
2311 if (history)
2312 OTGrammar_finalizeHistory (me, history.get(), numberOfData);
2313 OTGrammar_opt_deleteOutputMatching (me);
2314 if (history_out)
2315 *history_out = history.move();
2316 } catch (MelderError) {
2317 OTGrammar_opt_deleteOutputMatching (me);
2318 if (history_out)
2319 *history_out = history.move(); // so that we can inspect
2320 throw;
2321 }
2322 } catch (MelderError) {
2323 if (idatum > 1)
2324 Melder_appendError (U"Only ", idatum - 1, U" input-output pairs out of ", numberOfData, U" were processed.");
2325 Melder_throw (me, U" & ", thee, U": not learned from partial outputs.");
2326 }
2327 }
2328
OTGrammar_PairDistribution_getFractionCorrect(OTGrammar me,PairDistribution thee,double evaluationNoise,integer numberOfInputs)2329 double OTGrammar_PairDistribution_getFractionCorrect (OTGrammar me, PairDistribution thee,
2330 double evaluationNoise, integer numberOfInputs)
2331 {
2332 try {
2333 integer numberOfCorrect = 0;
2334 for (integer ireplication = 1; ireplication <= numberOfInputs; ireplication ++) {
2335 conststring32 input, adultOutput;
2336 PairDistribution_peekPair (thee, & input, & adultOutput);
2337 OTGrammar_newDisharmonies (me, evaluationNoise);
2338 integer inputTableau = OTGrammar_getTableau (me, input);
2339 const integer ilearnerCandidate = OTGrammar_getWinner (me, inputTableau);
2340 OTGrammarCandidate learnerCandidate = & my tableaus [inputTableau]. candidates [ilearnerCandidate];
2341 if (str32equ (learnerCandidate -> output.get(), adultOutput))
2342 numberOfCorrect ++;
2343 }
2344 return (double) numberOfCorrect / numberOfInputs;
2345 } catch (MelderError) {
2346 Melder_throw (me, U" & ", thee, U": fraction correct not computed.");
2347 }
2348 }
2349
OTGrammar_PairDistribution_getMinimumNumberCorrect(OTGrammar me,PairDistribution thee,double evaluationNoise,integer numberOfReplications)2350 integer OTGrammar_PairDistribution_getMinimumNumberCorrect (OTGrammar me, PairDistribution thee,
2351 double evaluationNoise, integer numberOfReplications)
2352 {
2353 try {
2354 integer minimumNumberCorrect = numberOfReplications;
2355 for (integer ipair = 1; ipair <= thy pairs.size; ipair ++) {
2356 PairProbability prob = thy pairs.at [ipair];
2357 if (prob -> weight > 0.0) {
2358 integer numberOfCorrect = 0;
2359 conststring32 input = prob -> string1.get(), adultOutput = prob -> string2.get();
2360 integer inputTableau = OTGrammar_getTableau (me, input);
2361 for (integer ireplication = 1; ireplication <= numberOfReplications; ireplication ++) {
2362 OTGrammar_newDisharmonies (me, evaluationNoise);
2363 const integer ilearnerCandidate = OTGrammar_getWinner (me, inputTableau);
2364 OTGrammarCandidate learnerCandidate = & my tableaus [inputTableau]. candidates [ilearnerCandidate];
2365 if (str32equ (learnerCandidate -> output.get(), adultOutput))
2366 numberOfCorrect ++;
2367 }
2368 if (numberOfCorrect < minimumNumberCorrect)
2369 minimumNumberCorrect = numberOfCorrect;
2370 }
2371 }
2372 return minimumNumberCorrect;
2373 } catch (MelderError) {
2374 Melder_throw (me, U" & ", thee, U": minimum number correct not computed.");
2375 }
2376 }
2377
OTGrammar_Distributions_getFractionCorrect(OTGrammar me,Distributions thee,integer columnNumber,double evaluationNoise,integer numberOfInputs)2378 double OTGrammar_Distributions_getFractionCorrect (OTGrammar me, Distributions thee, integer columnNumber,
2379 double evaluationNoise, integer numberOfInputs)
2380 {
2381 try {
2382 integer numberOfCorrect = 0;
2383 OTGrammar_Distributions_opt_createOutputMatching (me, thee, columnNumber);
2384 for (integer ireplication = 1; ireplication <= numberOfInputs; ireplication ++) {
2385 integer ipartialOutput;
2386 Distributions_peek (thee, columnNumber, nullptr, & ipartialOutput);
2387 OTGrammar_newDisharmonies (me, evaluationNoise);
2388 integer assumedAdultInputTableau, assumedAdultCandidate;
2389 OTGrammar_getInterpretiveParse_opt (me, ipartialOutput, & assumedAdultInputTableau, & assumedAdultCandidate);
2390 const integer ilearnerCandidate = OTGrammar_getWinner (me, assumedAdultInputTableau);
2391 OTGrammarCandidate learnerCandidate = & my tableaus [assumedAdultInputTableau]. candidates [ilearnerCandidate];
2392 if (str32equ (learnerCandidate -> output.get(), my tableaus [assumedAdultInputTableau]. candidates [assumedAdultCandidate]. output.get()))
2393 numberOfCorrect ++;
2394 }
2395 OTGrammar_opt_deleteOutputMatching (me);
2396 return (double) numberOfCorrect / numberOfInputs;
2397 } catch (MelderError) {
2398 Melder_throw (me, U" & ", thee, U": fraction correct not computed.");
2399 }
2400 }
2401
OTGrammar_removeConstraint(OTGrammar me,conststring32 constraintName)2402 void OTGrammar_removeConstraint (OTGrammar me, conststring32 constraintName) {
2403 try {
2404 integer removed = 0;
2405 Melder_require (my numberOfConstraints > 1,
2406 U"Cannot remove last remaining constraint.");
2407
2408 /*
2409 Look for the constraint to be removed.
2410 */
2411 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2412 OTGrammarConstraint constraint = & my constraints [icons];
2413 if (str32equ (constraint -> name.get(), constraintName)) {
2414 removed = icons;
2415 break;
2416 }
2417 }
2418 if (removed == 0)
2419 Melder_throw (U"No such constraint.");
2420 /*
2421 Remove the constraint while reusing the memory space.
2422 */
2423 my constraints [removed]. destroy ();
2424 Melder_assert (! my constraints [removed]. name);
2425 my constraints. remove (removed);
2426 my numberOfConstraints -= 1; // maintain invariant
2427 Melder_assert (my numberOfConstraints == my constraints.size);
2428 /*
2429 Remove or shift fixed rankings.
2430 */
2431 for (integer ifixed = my numberOfFixedRankings; ifixed > 0; ifixed --) {
2432 OTGrammarFixedRanking fixed = & my fixedRankings [ifixed];
2433 if (fixed -> higher == removed || fixed -> lower == removed) {
2434 /*
2435 Remove fixed ranking.
2436 */
2437 my fixedRankings [ifixed]. destroy();
2438 my fixedRankings. remove (ifixed);
2439 my numberOfFixedRankings -= 1; // maintain invariant
2440 Melder_assert (my numberOfFixedRankings == my fixedRankings.size);
2441 } else {
2442 /*
2443 Shift fixed ranking.
2444 */
2445 if (fixed -> higher > removed)
2446 fixed -> higher -= 1;
2447 if (fixed -> lower > removed)
2448 fixed -> lower -= 1;
2449 }
2450 }
2451 /*
2452 Shift tableau rows.
2453 */
2454 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2455 OTGrammarTableau tableau = & my tableaus [itab];
2456 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
2457 OTGrammarCandidate candidate = & tableau -> candidates [icand];
2458 candidate -> marks. remove (removed);
2459 candidate -> numberOfConstraints -= 1; // maintain invariant
2460 Melder_assert (candidate -> numberOfConstraints == candidate -> marks.size);
2461 }
2462 }
2463 /*
2464 Rebuild index.
2465 */
2466 my index. resize (my numberOfConstraints);
2467 for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
2468 my index [icons] = icons;
2469 OTGrammar_sort (me);
2470 } catch (MelderError) {
2471 Melder_throw (me, U": constraint \"", constraintName, U"\" not removed.");
2472 }
2473 }
2474
OTGrammarTableau_removeCandidate_unstripped(OTGrammarTableau me,integer candidateNumber)2475 static void OTGrammarTableau_removeCandidate_unstripped (OTGrammarTableau me, integer candidateNumber) {
2476 Melder_assert (candidateNumber >= 1);
2477 if (candidateNumber > my numberOfCandidates)
2478 Melder_fatal (U"icand ", candidateNumber, U", ncand ", my numberOfCandidates);
2479
2480 my candidates [candidateNumber]. destroy ();
2481 Melder_assert (! my candidates [candidateNumber]. output); // check leak
2482 Melder_assert (my candidates [candidateNumber]. marks.size == 0);
2483 Melder_assert (my candidates [candidateNumber]. marks.cells == nullptr); // check leak
2484 my candidates. remove (candidateNumber);
2485 my numberOfCandidates -= 1; // maintain invariant
2486 Melder_assert (my numberOfCandidates == my candidates.size);
2487 }
2488
OTGrammarTableau_isHarmonicallyBounded(OTGrammarTableau me,integer icand,integer jcand)2489 static bool OTGrammarTableau_isHarmonicallyBounded (OTGrammarTableau me, integer icand, integer jcand) {
2490 OTGrammarCandidate candi = & my candidates [icand], candj = & my candidates [jcand];
2491 bool equal = true;
2492 if (icand == jcand)
2493 return false;
2494 for (integer icons = 1; icons <= candi -> numberOfConstraints; icons ++) {
2495 if (candi -> marks [icons] < candj -> marks [icons])
2496 return false;
2497 if (candi -> marks [icons] > candj -> marks [icons])
2498 equal = false;
2499 }
2500 return ! equal;
2501 }
2502
OTGrammarTableau_candidateIsPossibleWinner(OTGrammar me,integer itab,integer icand)2503 static bool OTGrammarTableau_candidateIsPossibleWinner (OTGrammar me, integer itab, integer icand) {
2504 OTGrammar_save (me);
2505 try {
2506 OTGrammar_reset (me, 100.0);
2507 for (;;) {
2508 bool grammarHasChanged = false;
2509 OTGrammar_learnOne (me, my tableaus [itab]. input.get(), my tableaus [itab]. candidates [icand]. output.get(),
2510 1e-3, kOTGrammar_rerankingStrategy::EDCD, false, 1.0, 0.0, true, true, & grammarHasChanged);
2511 if (! grammarHasChanged) {
2512 OTGrammar_restore (me);
2513 return true;
2514 }
2515 double previousStratum = 101.0;
2516 OTGrammar_newDisharmonies (me, 0.0);
2517 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2518 const double stratum = my constraints [my index [icons]]. ranking;
2519 #if 0
2520 if (stratum < 50.0 - my numberOfConstraints) {
2521 OTGrammar_restore (me);
2522 return false; // we detected a tumble
2523 }
2524 #else
2525 if (stratum < previousStratum) {
2526 if (stratum < previousStratum - 1.0) {
2527 OTGrammar_restore (me);
2528 return false; // we detected a vacated stratum
2529 }
2530 previousStratum = stratum;
2531 }
2532 #endif
2533 }
2534 }
2535 } catch (MelderError) {
2536 OTGrammar_restore (me); // strong exception guarantee
2537 throw;
2538 }
2539 return false; // cannot occur
2540 }
2541
OTGrammar_removeHarmonicallyBoundedCandidates(OTGrammar me,bool singly)2542 void OTGrammar_removeHarmonicallyBoundedCandidates (OTGrammar me, bool singly) {
2543 try {
2544 /*
2545 First, the candidates that are harmonically bounded by one or more single other candidates have to be removed;
2546 otherwise, EDCD will stall.
2547 */
2548 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2549 OTGrammarTableau tab = & my tableaus [itab];
2550 for (integer icand = tab -> numberOfCandidates; icand >= 1; icand --) {
2551 for (integer jcand = 1; jcand <= tab -> numberOfCandidates; jcand ++) {
2552 if (OTGrammarTableau_isHarmonicallyBounded (tab, icand, jcand)) {
2553 OTGrammarTableau_removeCandidate_unstripped (tab, icand);
2554 break;
2555 }
2556 }
2557 }
2558 //tab -> candidates.shrinkToFit();
2559 }
2560 if (! singly) {
2561 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2562 OTGrammarTableau tab = & my tableaus [itab];
2563 for (integer icand = tab -> numberOfCandidates; icand >= 1; icand --) {
2564 if (! OTGrammarTableau_candidateIsPossibleWinner (me, itab, icand))
2565 OTGrammarTableau_removeCandidate_unstripped (tab, icand);
2566 }
2567 //tab -> candidates.shrinkToFit();
2568 }
2569 }
2570 } catch (MelderError) {
2571 Melder_throw (me, U": not all harmonically bounded candidates were removed.");
2572 }
2573 }
2574
Thing_define(OTGrammar_List4,Daata)2575 Thing_define (OTGrammar_List4, Daata) {
2576 // new data:
2577 integer hi1, lo1, hi2, lo2;
2578 };
2579
2580 Thing_implement (OTGrammar_List4, Daata, 0);
2581
OTGrammar_PairDistribution_listObligatoryRankings(OTGrammar me,PairDistribution thee)2582 void OTGrammar_PairDistribution_listObligatoryRankings (OTGrammar me, PairDistribution thee) {
2583 /*
2584 Save.
2585 */
2586 integer savedNumberOfFixedRankings = my numberOfFixedRankings;
2587 autovector <structOTGrammarFixedRanking> savedFixedRankings = my fixedRankings.move(); // BUG: is not restored upon throw
2588 OTGrammar_save (me);
2589 try {
2590 integer ipair = 0, npair = my numberOfConstraints * (my numberOfConstraints - 1);
2591 integer itrial;
2592 const double evaluationNoise = 1e-9;
2593 /*
2594 Add room for two more fixed rankings.
2595 */
2596 my fixedRankings = newvectorzero <structOTGrammarFixedRanking> (my numberOfFixedRankings + 2);
2597 for (integer ifixedRanking = 1; ifixedRanking <= my numberOfFixedRankings; ifixedRanking ++) {
2598 my fixedRankings [ifixedRanking]. higher = savedFixedRankings [ifixedRanking]. higher;
2599 my fixedRankings [ifixedRanking]. lower = savedFixedRankings [ifixedRanking]. lower;
2600 }
2601 /*
2602 Test whether there are rankings at all for these output data.
2603 */
2604 OTGrammar_reset (me, 100.0);
2605 for (itrial = 1; itrial <= 40; itrial ++) {
2606 bool grammarHasChangedDuringCycle = false;
2607 OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
2608 OTGrammar_newDisharmonies (me, evaluationNoise);
2609 for (integer iform = 1; iform <= thy pairs.size; iform ++) {
2610 PairProbability prob = thy pairs.at [iform];
2611 if (prob -> weight > 0.0) {
2612 bool grammarHasChanged = false;
2613 OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
2614 evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
2615 1.0, 0.0, false, true, & grammarHasChanged
2616 );
2617 if (grammarHasChanged)
2618 OTGrammar_newDisharmonies (me, evaluationNoise);
2619 grammarHasChangedDuringCycle |= grammarHasChanged;
2620 }
2621 }
2622 if (! grammarHasChangedDuringCycle)
2623 break;
2624 }
2625 if (itrial > 40) {
2626 MelderInfo_writeLine (U"There are no total rankings that generate these input-output pairs.");
2627 throw MelderError ();
2628 }
2629 /*
2630 Test learnability of every possible ranked pair.
2631 */
2632 my numberOfFixedRankings ++;
2633 autoBOOLMAT obligatory = zero_BOOLMAT (my numberOfConstraints, my numberOfConstraints);
2634 MelderInfo_open ();
2635 autoMelderProgress progress (U"Finding obligatory rankings.");
2636 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2637 for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons) {
2638 my fixedRankings [my numberOfFixedRankings]. higher = icons;
2639 my fixedRankings [my numberOfFixedRankings]. lower = jcons;
2640 OTGrammar_reset (me, 100.0);
2641 Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair, U": Trying ranking ",
2642 my constraints [icons]. name.get(), U" >> ", my constraints [jcons]. name.get());
2643 ipair ++;
2644 for (itrial = 1; itrial <= 40; itrial ++) {
2645 bool grammarHasChangedDuringCycle = false;
2646 OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
2647 OTGrammar_newDisharmonies (me, evaluationNoise);
2648 for (integer iform = 1; iform <= thy pairs.size; iform ++) {
2649 PairProbability prob = thy pairs.at [iform];
2650 if (prob -> weight > 0.0) {
2651 bool grammarHasChanged = false;
2652 OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
2653 evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
2654 1.0, 0.0, false, true, & grammarHasChanged);
2655 if (grammarHasChanged)
2656 OTGrammar_newDisharmonies (me, evaluationNoise);
2657 grammarHasChangedDuringCycle |= grammarHasChanged;
2658 }
2659 }
2660 if (! grammarHasChangedDuringCycle)
2661 break;
2662 }
2663 if (itrial > 40) {
2664 obligatory [jcons] [icons] = true;
2665 MelderInfo_writeLine (my constraints [jcons]. name.get(), U" >> ", my constraints [icons]. name.get());
2666 MelderInfo_close ();
2667 }
2668 }
2669 }
2670 my numberOfFixedRankings ++;
2671 Melder_progress (0.0, U"");
2672 npair = npair * npair;
2673 OrderedOf<structOTGrammar_List4> list;
2674 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2675 for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons && ! obligatory [jcons] [icons]) {
2676 my fixedRankings [my numberOfFixedRankings - 1]. higher = icons;
2677 my fixedRankings [my numberOfFixedRankings - 1]. lower = jcons;
2678 for (integer kcons = icons; kcons <= my numberOfConstraints; kcons ++) {
2679 for (integer lcons = 1; lcons <= my numberOfConstraints; lcons ++) if (kcons != lcons && ! obligatory [lcons] [kcons]) {
2680 if (icons == kcons && jcons >= lcons)
2681 continue;
2682 if (icons == lcons && jcons == kcons)
2683 continue;
2684 if (jcons == kcons && obligatory [lcons] [icons])
2685 continue;
2686 if (icons == lcons && obligatory [jcons] [kcons])
2687 continue;
2688 if (obligatory [lcons] [icons] && obligatory [jcons] [kcons])
2689 continue;
2690 my fixedRankings [my numberOfFixedRankings]. higher = kcons;
2691 my fixedRankings [my numberOfFixedRankings]. lower = lcons;
2692 OTGrammar_reset (me, 100.0);
2693 Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair);
2694 ipair ++;
2695 for (itrial = 1; itrial <= 40; itrial ++) {
2696 bool grammarHasChangedDuringCycle = false;
2697 OTGrammar_honourLocalRankings (me, 1.0, 0.0, & grammarHasChangedDuringCycle);
2698 OTGrammar_newDisharmonies (me, evaluationNoise);
2699 for (integer iform = 1; iform <= thy pairs.size; iform ++) {
2700 PairProbability prob = thy pairs.at [iform];
2701 if (prob -> weight > 0.0) {
2702 bool grammarHasChanged = false;
2703 OTGrammar_learnOne (me, prob -> string1.get(), prob -> string2.get(),
2704 evaluationNoise, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
2705 1.0, 0.0, false, true, & grammarHasChanged);
2706 if (grammarHasChanged)
2707 OTGrammar_newDisharmonies (me, evaluationNoise);
2708 grammarHasChangedDuringCycle |= grammarHasChanged;
2709 }
2710 }
2711 if (! grammarHasChangedDuringCycle)
2712 break;
2713 }
2714 if (itrial > 40) {
2715 autoOTGrammar_List4 listElement = Thing_new (OTGrammar_List4);
2716 listElement -> hi1 = jcons;
2717 listElement -> lo1 = icons;
2718 listElement -> hi2 = lcons;
2719 listElement -> lo2 = kcons;
2720 list. addItem_move (listElement.move());
2721 }
2722 }
2723 }
2724 }
2725 }
2726 Melder_progress (1.0);
2727 /*
2728 Improve list.
2729 */
2730 bool improved = true;
2731 while (improved) {
2732 improved = false;
2733 for (integer ilist = 1; ilist <= list.size; ilist ++) {
2734 for (integer jlist = 1; jlist <= list.size; jlist ++) if (ilist != jlist) {
2735 OTGrammar_List4 elA = list.at [ilist];
2736 OTGrammar_List4 elB = list.at [jlist];
2737 integer ahi1 = elA -> hi1, alo1 = elA -> lo1, ahi2 = elA -> hi2, alo2 = elA -> lo2;
2738 integer bhi1 = elB -> hi1, blo1 = elB -> lo1, bhi2 = elB -> hi2, blo2 = elB -> lo2;
2739 improved |= (ahi1 == bhi1 || obligatory [bhi1] [ahi1]) && (ahi2 == bhi2 || obligatory [bhi2] [ahi2]) &&
2740 (alo1 == blo1 || obligatory [alo1] [blo1]) && (alo2 == blo2 || obligatory [alo2] [blo2]);
2741 improved |= (ahi1 == bhi2 || obligatory [bhi2] [ahi1]) && (ahi2 == bhi1 || obligatory [bhi1] [ahi2]) &&
2742 (alo1 == blo2 || obligatory [alo1] [blo2]) && (alo2 == blo1 || obligatory [alo2] [blo1]);
2743 if (improved) {
2744 list. removeItem (jlist);
2745 break;
2746 }
2747 }
2748 if (improved) break;
2749 }
2750 }
2751 improved = true;
2752 while (improved) {
2753 improved = false;
2754 for (integer ilist = 1; ilist <= list.size; ilist ++) {
2755 for (integer jlist = 1; jlist <= list.size; jlist ++) if (ilist != jlist) {
2756 OTGrammar_List4 elA = list.at [ilist];
2757 OTGrammar_List4 elB = list.at [jlist];
2758 integer ahi1 = elA -> hi1, alo1 = elA -> lo1, ahi2 = elA -> hi2, alo2 = elA -> lo2;
2759 integer bhi1 = elB -> hi1, blo1 = elB -> lo1, bhi2 = elB -> hi2, blo2 = elB -> lo2;
2760 improved |= ahi1 == bhi1 && alo1 == blo1 && ahi2 == bhi2 && blo2 == bhi1 && alo2 == alo1;
2761 improved |= ahi1 == bhi2 && alo1 == blo2 && ahi2 == bhi1 && blo1 == bhi2 && alo2 == alo1;
2762 improved |= ahi2 == bhi1 && alo2 == blo1 && ahi1 == bhi2 && blo2 == bhi1 && alo1 == alo2;
2763 improved |= ahi2 == bhi2 && alo2 == blo2 && ahi1 == bhi1 && blo1 == bhi2 && alo1 == alo2;
2764 if (improved) {
2765 list. removeItem (jlist);
2766 break;
2767 }
2768 }
2769 if (improved) break;
2770 }
2771 }
2772 for (integer ilist = 1; ilist <= list.size; ilist ++) {
2773 OTGrammar_List4 el = list.at [ilist];
2774 MelderInfo_write (my constraints [el -> hi1]. name.get(), U" >> ", my constraints [el -> lo1]. name.get(), U" OR ");
2775 MelderInfo_writeLine (my constraints [el -> hi2]. name.get(), U" >> ", my constraints [el -> lo2]. name.get());
2776 MelderInfo_close ();
2777 }
2778 MelderInfo_close ();
2779
2780 /*
2781 Restore.
2782 */
2783 my numberOfFixedRankings = savedNumberOfFixedRankings;
2784 my fixedRankings = savedFixedRankings.move();
2785 OTGrammar_restore (me);
2786 } catch (MelderError) {
2787 MelderInfo_close ();
2788 /*
2789 Restore.
2790 */
2791 my numberOfFixedRankings = savedNumberOfFixedRankings;
2792 my fixedRankings = savedFixedRankings.move();
2793 OTGrammar_restore (me);
2794 Melder_throw (me, U": obligatory rankings not listed.");
2795 }
2796 }
2797
OTGrammar_Distributions_listObligatoryRankings(OTGrammar me,Distributions thee,integer columnNumber)2798 void OTGrammar_Distributions_listObligatoryRankings (OTGrammar me, Distributions thee, integer columnNumber) {
2799 /*
2800 Save.
2801 */
2802 autovector <structOTGrammarFixedRanking> savedFixedRankings = my fixedRankings.move();
2803 OTGrammar_save (me);
2804 try {
2805 integer ipair = 0, npair = my numberOfConstraints * (my numberOfConstraints - 1);
2806 /*
2807 Add room for one more fixed ranking.
2808 */
2809 my numberOfFixedRankings ++;
2810 my fixedRankings = newvectorzero <structOTGrammarFixedRanking> (my numberOfFixedRankings);
2811 for (integer ifixedRanking = 1; ifixedRanking < my numberOfFixedRankings; ifixedRanking ++) {
2812 my fixedRankings [ifixedRanking]. higher = savedFixedRankings [ifixedRanking]. higher;
2813 my fixedRankings [ifixedRanking]. lower = savedFixedRankings [ifixedRanking]. lower;
2814 }
2815 /*
2816 Test learnability of every possible ranked pair.
2817 */
2818 MelderInfo_open ();
2819 autoMelderProgress progress (U"Finding obligatory rankings.");
2820 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2821 for (integer jcons = 1; jcons <= my numberOfConstraints; jcons ++) if (icons != jcons) {
2822 my fixedRankings [my numberOfFixedRankings]. higher = icons;
2823 my fixedRankings [my numberOfFixedRankings]. lower = jcons;
2824 OTGrammar_reset (me, 100.0);
2825 Melder_progress ((double) ipair / npair, ipair + 1, U"/", npair, U": Trying ranking ",
2826 my constraints [icons]. name.get(), U" >> ", my constraints [jcons]. name.get());
2827 ipair ++;
2828 Melder_progressOff ();
2829 OTGrammar_Distributions_learnFromPartialOutputs (me, thee, columnNumber,
2830 1e-9, kOTGrammar_rerankingStrategy::EDCD, true /* honour fixed rankings; very important */,
2831 1.0, 1000, 0.0, 1, 0.0, 1, 0, nullptr, false, false, 0);
2832 Melder_progressOn ();
2833 for (integer kcons = 1; kcons <= my numberOfConstraints; kcons ++) {
2834 if (my constraints [kcons]. ranking < 0.0) {
2835 MelderInfo_writeLine (my constraints [jcons]. name.get(), U" >> ", my constraints [icons]. name.get());
2836 break;
2837 }
2838 }
2839 }
2840 }
2841 MelderInfo_close ();
2842 /*
2843 Restore.
2844 */
2845 my numberOfFixedRankings --;
2846 my fixedRankings = savedFixedRankings.move();
2847 OTGrammar_restore (me);
2848 } catch (MelderError) {
2849 MelderInfo_close ();
2850 /*
2851 Restore.
2852 */
2853 my numberOfFixedRankings --;
2854 my fixedRankings = savedFixedRankings.move();
2855 OTGrammar_restore (me);
2856 Melder_throw (me, U": obligatory rankings not listed.");
2857 }
2858 }
2859
printConstraintNames(OTGrammar me,MelderString * buffer)2860 static void printConstraintNames (OTGrammar me, MelderString *buffer) {
2861 char32 text [200];
2862 bool secondLine = false;
2863 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2864 OTGrammarConstraint constraint = & my constraints [my index [icons]];
2865 if (str32chr (constraint -> name.get(), U'\n')) {
2866 char32 *newLine;
2867 str32cpy (text, constraint -> name.get());
2868 newLine = str32chr (text, U'\n');
2869 *newLine = U'\0';
2870 MelderString_append (buffer, U"\t", text);
2871 secondLine = true;
2872 } else {
2873 MelderString_append (buffer, U"\t", constraint -> name.get());
2874 }
2875 }
2876 MelderString_appendCharacter (buffer, U'\n');
2877 if (secondLine) {
2878 MelderString_appendCharacter (buffer, U'\t');
2879 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2880 OTGrammarConstraint constraint = & my constraints [my index [icons]];
2881 char32 *newLine = str32chr (constraint -> name.get(), U'\n');
2882 MelderString_append (buffer, U"\t", newLine ? newLine + 1 : U"");
2883 }
2884 MelderString_appendCharacter (buffer, U'\n');
2885 }
2886 }
2887
OTGrammar_writeToHeaderlessSpreadsheetFile(OTGrammar me,MelderFile file)2888 void OTGrammar_writeToHeaderlessSpreadsheetFile (OTGrammar me, MelderFile file) {
2889 try {
2890 autoMelderString buffer;
2891 MelderString_copy (& buffer, U"CONSTRAINTS\t");
2892 printConstraintNames (me, & buffer);
2893 MelderString_append (& buffer, U"rankings\t");
2894 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2895 OTGrammarConstraint constraint = & my constraints [my index [icons]];
2896 MelderString_append (& buffer, U"\t", constraint -> ranking);
2897 }
2898 MelderString_append (& buffer, U"\ndisharmonies\t");
2899 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2900 OTGrammarConstraint constraint = & my constraints [my index [icons]];
2901 MelderString_append (& buffer, U"\t", constraint -> disharmony);
2902 }
2903 MelderString_appendCharacter (& buffer, U'\n');
2904 for (integer itab = 1; itab <= my numberOfTableaus; itab ++) {
2905 OTGrammarTableau tableau = & my tableaus [itab];
2906 integer winner = OTGrammar_getWinner (me, itab), numberOfOptimalCandidates = 0;
2907 for (integer icons = 1; icons <= my numberOfConstraints + 1; icons ++)
2908 MelderString_appendCharacter (& buffer, U'\t');
2909 MelderString_append (& buffer, U"\nINPUT\t", tableau -> input.get());
2910 printConstraintNames (me, & buffer);
2911 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
2912 if (OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0)
2913 numberOfOptimalCandidates ++;
2914 }
2915 for (integer icand = 1; icand <= tableau -> numberOfCandidates; icand ++) {
2916 OTGrammarCandidate candidate = & tableau -> candidates [icand];
2917 bool candidateIsOptimal = OTGrammar_compareCandidates (me, itab, icand, itab, winner) == 0;
2918 integer crucialCell = OTGrammar_crucialCell (me, itab, icand, winner, numberOfOptimalCandidates);
2919 MelderString_append (& buffer,
2920 candidateIsOptimal == false ? U"loser" : numberOfOptimalCandidates > 1 ? U"co-winner" : U"winner",
2921 U"\t",
2922 candidate -> output.get());
2923 for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
2924 integer index = my index [icons];
2925 OTGrammarConstraint constraint = & my constraints [index];
2926 static MelderString markString;
2927 MelderString_empty (& markString);
2928 /*
2929 An exclamation mark can be drawn in this cell only if all of the following conditions are met:
2930 1. the candidate is not optimal;
2931 2. the constraint is not tied;
2932 3. this is the crucial cell, i.e. the cells after it are drawn in grey.
2933 */
2934 if (icons == crucialCell && ! candidateIsOptimal && ! constraint -> tiedToTheLeft && ! constraint -> tiedToTheRight) {
2935 const integer winnerMarks = tableau -> candidates [winner]. marks [index];
2936 for (integer imark = 1; imark <= winnerMarks + 1; imark ++)
2937 MelderString_appendCharacter (& markString, U'*');
2938 MelderString_appendCharacter (& markString, U'!');
2939 for (integer imark = winnerMarks + 2; imark <= candidate -> marks [index]; imark ++)
2940 MelderString_appendCharacter (& markString, U'*');
2941 } else {
2942 if (! candidateIsOptimal && (constraint -> tiedToTheLeft || constraint -> tiedToTheRight) &&
2943 crucialCell >= 1 && constraint -> disharmony == my constraints [my index [crucialCell]]. disharmony)
2944 {
2945 MelderString_appendCharacter (& markString, U'=');
2946 }
2947 for (integer imark = 1; imark <= candidate -> marks [index]; imark ++)
2948 MelderString_appendCharacter (& markString, U'*');
2949 }
2950 MelderString_append (& buffer, U"\t", markString.string);
2951 }
2952 MelderString_appendCharacter (& buffer, U'\n');
2953 }
2954 }
2955 MelderFile_writeText (file, buffer.string, Melder_getOutputEncoding ());
2956 } catch (MelderError) {
2957 Melder_throw (me, U": not saved to tab-separated file ", file, U".");
2958 }
2959 }
2960
2961 /* End of file OTGrammar.cpp */
2962