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