1 /* OTMulti.cpp
2  *
3  * Copyright (C) 2005-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 2005/06/11 the very beginning of computational bidirectional multi-level OT
21  * pb 2006/05/16 guarded against cells with many violations
22  * pb 2006/05/17 draw disharmonies above tableau
23  * pb 2007/05/19 decision strategies
24  * pb 2007/08/12 wchar
25  * pb 2007/10/01 leak and constraint plasticity
26  * pb 2007/10/01 can write as encoding
27  * pb 2007/11/14 drawTableau: corrected direction of arrows for positive satisfactions
28  * pb 2008/04/14 OTMulti_getConstraintIndexFromName
29  * pb 2009/03/18 modern enums
30  * pb 2010/06/05 corrected colours
31  * pb 2011/03/01 multiple update rules; more decision strategies
32  * pb 2011/03/29 C++
33  * pb 2011/04/27 Melder_debug 41 and 42
34  * pb 2011/05/15 prevented zero sums of probabilities in MaxEnt
35  */
36 
37 #include "OTMulti.h"
38 
39 #include "oo_DESTROY.h"
40 #include "OTMulti_def.h"
41 #include "oo_COPY.h"
42 #include "OTMulti_def.h"
43 #include "oo_EQUAL.h"
44 #include "OTMulti_def.h"
45 #include "oo_CAN_WRITE_AS_ENCODING.h"
46 #include "OTMulti_def.h"
47 #include "oo_WRITE_BINARY.h"
48 #include "OTMulti_def.h"
49 #include "oo_READ_BINARY.h"
50 #include "OTMulti_def.h"
51 #include "oo_DESCRIPTION.h"
52 #include "OTMulti_def.h"
53 
54 void structOTMulti :: v_info ()
55 {
56 	structDaata :: v_info ();
57 	integer numberOfViolations = 0;
58 	for (integer icand = 1; icand <= our numberOfCandidates; icand ++)
59 		for (integer icons = 1; icons <= our numberOfConstraints; icons ++)
60 			numberOfViolations += our candidates [icand]. marks [icons];
61 	MelderInfo_writeLine (U"Decision strategy: ", kOTGrammar_decisionStrategy_getText (decisionStrategy));
62 	MelderInfo_writeLine (U"Number of constraints: ", our numberOfConstraints);
63 	MelderInfo_writeLine (U"Number of candidates: ", our numberOfCandidates);
64 	MelderInfo_writeLine (U"Number of violation marks: ", numberOfViolations);
65 }
66 
67 void structOTMulti :: v_writeText (MelderFile file) {
68 	MelderFile_write (file, U"\n<", kOTGrammar_decisionStrategy_getText (decisionStrategy),
69 		U">\n", leak, U" ! leak\n", our numberOfConstraints, U" constraints");
70 	for (integer icons = 1; icons <= our numberOfConstraints; icons ++) {
71 		OTConstraint constraint = & our constraints [icons];
72 		MelderFile_write (file, U"\n\t\"");
73 		for (const char32 *p = & constraint -> name [0]; *p != U'\0'; p ++) {
74 			if (*p == U'\"')
75 				MelderFile_writeCharacter (file, U'\"');   // double any quotes within quotes
76 			MelderFile_writeCharacter (file, *p);
77 		}
78 		MelderFile_write (file, U"\" ", constraint -> ranking,
79 				U" ", constraint -> disharmony, U" ", constraint -> plasticity);
80 	}
81 	MelderFile_write (file, U"\n\n", our numberOfCandidates, U" candidates");
82 	for (integer icand = 1; icand <= our numberOfCandidates; icand ++) {
83 		OTCandidate candidate = & our candidates [icand];
84 		MelderFile_write (file, U"\n\t\"");
85 		for (const char32 *p = & candidate -> string [0]; *p != U'\0'; p ++) {
86 			if (*p == U'\"')
87 				MelderFile_writeCharacter (file, U'\"');   // double any quotes within quotes
88 			MelderFile_writeCharacter (file, *p);
89 		}
90 		MelderFile_write (file, U"\"  ");
91 		for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++)
92 			MelderFile_write (file, U" ", candidate -> marks [icons]);
93 	}
94 }
95 
96 void OTMulti_checkIndex (OTMulti me) {
97 	if (my index.size != 0)
98 		return;
99 	my index = to_INTVEC (my numberOfConstraints);
100 	OTMulti_sort (me);
101 }
102 
103 void structOTMulti :: v_readText (MelderReadText text, int formatVersion) {
104 	OTMulti_Parent :: v_readText (text, formatVersion);
105 	if (formatVersion >= 1) {
106 		try {
107 			decisionStrategy = (kOTGrammar_decisionStrategy) texgete8 (text, (enum_generic_getValue) kOTGrammar_decisionStrategy_getValue);
108 		} catch (MelderError) {
109 			Melder_throw (U"Decision strategy not read.");
110 		}
111 	}
112 	if (formatVersion >= 2) {
113 		try {
114 			leak = texgetr64 (text);
115 		} catch (MelderError) {
116 			Melder_throw (U"Trying to read leak.");
117 		}
118 	}
119 	if ((our numberOfConstraints = texgeti32 (text)) < 1)
120 		Melder_throw (U"No constraints.");
121 	our constraints = newvectorzero <structOTConstraint> (our numberOfConstraints);
122 	for (integer icons = 1; icons <= our numberOfConstraints; icons ++) {
123 		OTConstraint constraint = & our constraints [icons];
124 		constraint -> name = texgetw16 (text);
125 		constraint -> ranking = texgetr64 (text);
126 		constraint -> disharmony = texgetr64 (text);
127 		if (formatVersion < 2) {
128 			constraint -> plasticity = 1.0;
129 		} else {
130 			try {
131 				constraint -> plasticity = texgetr64 (text);
132 			} catch (MelderError) {
133 				Melder_throw (U"Plasticity of constraint ", icons, U" not read.");
134 			}
135 		}
136 	}
137 	if ((numberOfCandidates = texgeti32 (text)) < 1)
138 		Melder_throw (U"No candidates.");
139 	our candidates = newvectorzero <structOTCandidate> (numberOfCandidates);
140 	for (integer icand = 1; icand <= numberOfCandidates; icand ++) {
141 		OTCandidate candidate = & our candidates [icand];
142 		candidate -> string = texgetw16 (text);
143 		candidate -> numberOfConstraints = our numberOfConstraints;   // redundancy, needed for writing binary
144 		candidate -> marks = raw_INTVEC (candidate -> numberOfConstraints);
145 		for (integer icons = 1; icons <= candidate -> numberOfConstraints; icons ++)
146 			candidate -> marks [icons] = texgeti16 (text);
147 	}
148 	OTMulti_checkIndex (this);
149 }
150 
151 Thing_implement (OTMulti, Daata, 2);
152 
153 integer OTMulti_getConstraintIndexFromName (OTMulti me, conststring32 name) {
154 	for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
155 		if (Melder_equ (my constraints [icons]. name.get(), name))
156 			return icons;
157 	return 0;
158 }
159 
160 void OTMulti_sort (OTMulti me) {
161 	std::sort (my index.begin(), my index.end(),
162 		[me] (integer icons, integer jcons) {
163 			OTConstraint ci = & my constraints [icons], cj = & my constraints [jcons];
164 			/*
165 				Sort primarily by disharmony.
166 			*/
167 			if (ci -> disharmony > cj -> disharmony)
168 				return true;
169 			if (ci -> disharmony < cj -> disharmony)
170 				return false;
171 			/*
172 				Tied constraints are sorted alphabetically.
173 			*/
174 			return str32cmp (my constraints [icons]. name.get(), my constraints [jcons]. name.get()) < 0;
175 		}
176 	);
177 	for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
178 		const OTConstraint constraint = & my constraints [my index [icons]];
179 		constraint -> tiedToTheLeft = ( icons > 1 &&
180 				my constraints [my index [icons - 1]]. disharmony == constraint -> disharmony );
181 		constraint -> tiedToTheRight = ( icons < my numberOfConstraints &&
182 				my constraints [my index [icons + 1]]. disharmony == constraint -> disharmony );
183 	}
184 }
185 
186 void OTMulti_newDisharmonies (OTMulti me, double evaluationNoise) {
187 	for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
188 		const OTConstraint constraint = & my constraints [icons];
189 		constraint -> disharmony = constraint -> ranking + NUMrandomGauss (0, evaluationNoise);
190 	}
191 	OTMulti_sort (me);
192 }
193 
194 int OTMulti_compareCandidates (OTMulti me, integer icand1, integer icand2) {
195 	INTVEC marks1 = my candidates [icand1]. marks.get();
196 	INTVEC marks2 = my candidates [icand2]. marks.get();
197 	if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
198 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
199 			integer numberOfMarks1 = marks1 [my index [icons]];
200 			integer numberOfMarks2 = marks2 [my index [icons]];
201 			/*
202 				Count tied constraints as one.
203 			*/
204 			while (my constraints [my index [icons]]. tiedToTheRight) {
205 				icons ++;
206 				numberOfMarks1 += marks1 [my index [icons]];
207 				numberOfMarks2 += marks2 [my index [icons]];
208 			}
209 			if (numberOfMarks1 < numberOfMarks2)
210 				return -1;   // candidate 1 is better than candidate 2
211 			if (numberOfMarks1 > numberOfMarks2)
212 				return +1;   // candidate 2 is better than candidate 1
213 		}
214 	} else if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
215 		my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
216 	{
217 		double disharmony1 = 0.0, disharmony2 = 0.0;
218 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
219 			disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
220 			disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
221 		}
222 		if (disharmony1 < disharmony2)
223 			return -1;   // candidate 1 is better than candidate 2
224 		if (disharmony1 > disharmony2)
225 			return +1;   // candidate 2 is better than candidate 1
226 	} else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
227 		double disharmony1 = 0.0, disharmony2 = 0.0;
228 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
229 			if (my constraints [icons]. disharmony > 0.0) {
230 				disharmony1 += my constraints [icons]. disharmony * marks1 [icons];
231 				disharmony2 += my constraints [icons]. disharmony * marks2 [icons];
232 			}
233 		}
234 		if (disharmony1 < disharmony2)
235 			return -1;   // candidate 1 is better than candidate 2
236 		if (disharmony1 > disharmony2)
237 			return +1;   // candidate 2 is better than candidate 1
238 	} else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
239 		my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
240 	{
241 		double disharmony1 = 0.0, disharmony2 = 0.0;
242 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
243 			disharmony1 += exp (my constraints [icons]. disharmony) * marks1 [icons];
244 			disharmony2 += exp (my constraints [icons]. disharmony) * marks2 [icons];
245 		}
246 		if (disharmony1 < disharmony2)
247 			return -1;   // candidate 1 is better than candidate 2
248 		if (disharmony1 > disharmony2)
249 			return +1;   // candidate 2 is better than candidate 1
250 	} else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
251 		double disharmony1 = 0.0, disharmony2 = 0.0;
252 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
253 			double constraintDisharmony = std::max (my constraints [icons]. disharmony, 1.0);
254 			disharmony1 += constraintDisharmony * marks1 [icons];
255 			disharmony2 += constraintDisharmony * marks2 [icons];
256 		}
257 		if (disharmony1 < disharmony2)
258 			return -1;   // candidate 1 is better than candidate 2
259 		if (disharmony1 > disharmony2)
260 			return +1;   // candidate 2 is better than candidate 1
261 	} else {
262 		Melder_fatal (U"Unimplemented decision strategy.");
263 	}
264 	return 0;   // none of the comparisons found a difference between the two candidates; hence, they are equally good
265 }
266 
267 int OTMulti_candidateMatches (OTMulti me, integer icand, conststring32 form1, conststring32 form2) {
268 	conststring32 string = my candidates [icand]. string.get();
269 	return (form1 [0] == U'\0' || str32str (string, form1)) && (form2 [0] == U'\0' || str32str (string, form2));
270 }
271 
272 static void _OTMulti_fillInHarmonies (OTMulti me, conststring32 form1, conststring32 form2) {
273 	if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) return;
274 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
275 		OTCandidate candidate = & my candidates [icand];
276 		INTVEC marks = candidate -> marks.get();
277 		double disharmony = 0.0;
278 		if (my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
279 			my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY)
280 		{
281 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
282 				disharmony += my constraints [icons]. disharmony * marks [icons];
283 		} else if (my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
284 			my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
285 		{
286 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
287 				disharmony += exp (my constraints [icons]. disharmony) * marks [icons];
288 		} else if (my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT) {
289 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
290 				if (my constraints [icons]. disharmony > 0.0)
291 					disharmony += my constraints [icons]. disharmony * marks [icons];
292 		} else if (my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG) {
293 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
294 				double constraintDisharmony = std::max (my constraints [icons]. disharmony, 1.0);
295 				disharmony += constraintDisharmony * marks [icons];
296 			}
297 		} else {
298 			Melder_fatal (U"_OTMulti_fillInHarmonies: unimplemented decision strategy.");
299 		}
300 		candidate -> harmony = - disharmony;
301 	}
302 }
303 
304 static void _OTMulti_fillInProbabilities (OTMulti me, conststring32 form1, conststring32 form2) {
305 	double maximumHarmony = -1e308;
306 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
307 		OTCandidate candidate = & my candidates [icand];
308 		if (candidate -> harmony > maximumHarmony)
309 			maximumHarmony = candidate -> harmony;
310 	}
311 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
312 		OTCandidate candidate = & my candidates [icand];
313 		candidate -> probability = exp (candidate -> harmony - maximumHarmony);
314 		Melder_assert (candidate -> probability >= 0.0 && candidate -> probability <= 1.0);
315 	}
316 	double sumOfProbabilities = 0.0;
317 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
318 		OTCandidate candidate = & my candidates [icand];
319 		sumOfProbabilities += candidate -> probability;
320 	}
321 	Melder_assert (sumOfProbabilities > 0.0);   // Because at least one of them is 1.0.
322 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
323 		OTCandidate candidate = & my candidates [icand];
324 		candidate -> probability /= sumOfProbabilities;
325 	}
326 }
327 
328 class MelderError_OTMulti_NoMatchingCandidate: public MelderError {};
329 
330 integer OTMulti_getWinner (OTMulti me, conststring32 form1, conststring32 form2) {
331 	try {
332 		integer icand_best = 0;
333 		if (my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
334 			my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY)
335 		{
336 			_OTMulti_fillInHarmonies (me, form1, form2);
337 			_OTMulti_fillInProbabilities (me, form1, form2);
338 			const double cutOff = NUMrandomUniform (0.0, 1.0);
339 			longdouble sumOfProbabilities = 0.0;
340 			for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
341 				sumOfProbabilities += my candidates [icand]. probability;
342 				if (sumOfProbabilities > cutOff) {
343 					icand_best = icand;
344 					break;
345 				}
346 			}
347 		} else {
348 			integer numberOfBestCandidates = 0;
349 			for (integer icand = 1; icand <= my numberOfCandidates; icand ++) if (OTMulti_candidateMatches (me, icand, form1, form2)) {
350 				if (icand_best == 0) {
351 					icand_best = icand;
352 					numberOfBestCandidates = 1;
353 				} else {
354 					int comparison = OTMulti_compareCandidates (me, icand, icand_best);
355 					if (comparison == -1) {
356 						icand_best = icand;   // the current candidate is the unique best candidate found so far
357 						numberOfBestCandidates = 1;
358 					} else if (comparison == 0) {
359 						numberOfBestCandidates += 1;   // the current candidate is equally good as the best found before
360 						/*
361 							Give all candidates that are equally good an equal chance to become the winner.
362 						*/
363 						if (Melder_debug == 41)
364 							icand_best = icand_best;   // keep first
365 						else if (Melder_debug == 42)
366 							icand_best = icand;   // take last
367 						else if (NUMrandomUniform (0.0, numberOfBestCandidates) < 1.0)   // default: take random
368 							icand_best = icand;
369 					}
370 				}
371 			}
372 		}
373 		if (icand_best == 0) {
374 			Melder_appendError (U"The forms ", form1, U" and ", form2, U" do not match any candidate.");
375 			throw MelderError_OTMulti_NoMatchingCandidate ();   // BUG: NYI
376 		}
377 		return icand_best;
378 	} catch (MelderError) {
379 		Melder_throw (me, U": winner not determined.");
380 	}
381 }
382 
383 static void OTMulti_modifyRankings (OTMulti me, integer iwinner, integer iloser,
384 	kOTGrammar_rerankingStrategy updateRule,
385 	double plasticity, double relativePlasticityNoise)
386 {
387 	bool *grammarHasChanged = nullptr;   // to be implemented
388 	bool warnIfStalled = false;   // to be implemented
389 	if (iwinner == iloser)
390 		return;
391 	OTCandidate winner = & my candidates [iwinner], loser = & my candidates [iloser];
392 	double step = relativePlasticityNoise == 0.0 ? plasticity : NUMrandomGauss (plasticity, relativePlasticityNoise * plasticity);
393 	bool multiplyStepByNumberOfViolations =
394 		my decisionStrategy == kOTGrammar_decisionStrategy::HARMONIC_GRAMMAR ||
395 		my decisionStrategy == kOTGrammar_decisionStrategy::LINEAR_OT ||
396 		my decisionStrategy == kOTGrammar_decisionStrategy::MAXIMUM_ENTROPY ||
397 		my decisionStrategy == kOTGrammar_decisionStrategy::POSITIVE_HG ||
398 		my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG ||
399 		my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_MAXIMUM_ENTROPY;
400 	if (Melder_debug != 0) {
401 		/*
402 			Perhaps override the standard update rule.
403 		*/
404 		if (Melder_debug == 26)
405 			multiplyStepByNumberOfViolations = false;   // OT-GLA
406 		else if (Melder_debug == 27)
407 			multiplyStepByNumberOfViolations = true;   // HG-GLA
408 	}
409 	if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ONE) {
410 		const integer icons = NUMrandomInteger (1, my numberOfConstraints);
411 		const OTConstraint constraint = & my constraints [icons];
412 		double constraintStep = step * constraint -> plasticity;
413 		const integer winnerMarks = winner -> marks [icons];
414 		const integer loserMarks = loser -> marks [icons];
415 		if (loserMarks > winnerMarks) {
416 			if (multiplyStepByNumberOfViolations)
417 				constraintStep *= loserMarks - winnerMarks;
418 			constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
419 			if (grammarHasChanged)
420 				*grammarHasChanged = true;
421 		}
422 		if (winnerMarks > loserMarks) {
423 			if (multiplyStepByNumberOfViolations)
424 				constraintStep *= winnerMarks - loserMarks;
425 			constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
426 			if (grammarHasChanged)
427 				*grammarHasChanged = true;
428 		}
429 	} else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL) {
430 		bool changed = false;
431 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
432 			const OTConstraint constraint = & my constraints [icons];
433 			double constraintStep = step * constraint -> plasticity;
434 			const integer winnerMarks = winner -> marks [icons];
435 			const integer loserMarks = loser -> marks [icons];
436 			if (loserMarks > winnerMarks) {
437 				if (multiplyStepByNumberOfViolations)
438 					constraintStep *= loserMarks - winnerMarks;
439 				constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
440 				changed = true;
441 			}
442 			if (winnerMarks > loserMarks) {
443 				if (multiplyStepByNumberOfViolations)
444 					constraintStep *= winnerMarks - loserMarks;
445 				constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
446 				changed = true;
447 			}
448 		}
449 		if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG) {
450 			longdouble sumOfWeights = 0.0;
451 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
452 				sumOfWeights += my constraints [icons]. ranking;
453 			const double averageWeight = double (sumOfWeights) / my numberOfConstraints;
454 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
455 				my constraints [icons]. ranking -= averageWeight;
456 		}
457 		if (grammarHasChanged)
458 			*grammarHasChanged = changed;
459 	} else if (updateRule == kOTGrammar_rerankingStrategy::SYMMETRIC_ALL_SKIPPABLE) {
460 		bool changed = false;
461 		integer winningConstraints = 0, losingConstraints = 0;
462 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
463 			const integer winnerMarks = winner -> marks [icons];
464 			const integer loserMarks = loser -> marks [icons];
465 			if (loserMarks > winnerMarks)
466 				losingConstraints ++;
467 			if (winnerMarks > loserMarks)
468 				winningConstraints ++;
469 		}
470 		if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
471 			const OTConstraint constraint = & my constraints [icons];
472 			double constraintStep = step * constraint -> plasticity;
473 			const integer winnerMarks = winner -> marks [icons];
474 			const integer loserMarks = loser -> marks [icons];
475 			if (loserMarks > winnerMarks) {
476 				if (multiplyStepByNumberOfViolations)
477 					constraintStep *= loserMarks - winnerMarks;
478 				constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak);
479 				changed = true;
480 			}
481 			if (winnerMarks > loserMarks) {
482 				if (multiplyStepByNumberOfViolations)
483 					constraintStep *= winnerMarks - loserMarks;
484 				constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak);
485 				changed = true;
486 			}
487 		}
488 		if (changed && my decisionStrategy == kOTGrammar_decisionStrategy::EXPONENTIAL_HG) {
489 			longdouble sumOfWeights = 0.0;
490 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
491 				sumOfWeights += my constraints [icons]. ranking;
492 			const double averageWeight = double (sumOfWeights) / my numberOfConstraints;
493 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
494 				my constraints [icons]. ranking -= averageWeight;
495 		}
496 		if (grammarHasChanged) *grammarHasChanged = changed;
497 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_UNCANCELLED) {
498 		integer winningConstraints = 0, losingConstraints = 0;
499 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
500 			const integer winnerMarks = winner -> marks [icons];
501 			const integer loserMarks = loser -> marks [icons];
502 			if (loserMarks > winnerMarks)
503 				losingConstraints ++;
504 			if (winnerMarks > loserMarks)
505 				winningConstraints ++;
506 		}
507 		if (winningConstraints != 0) {
508 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
509 				const OTConstraint constraint = & my constraints [icons];
510 				double constraintStep = step * constraint -> plasticity;
511 				const integer winnerMarks = winner -> marks [icons];
512 				const integer loserMarks = loser -> marks [icons];
513 				if (loserMarks > winnerMarks) {
514 					if (multiplyStepByNumberOfViolations)
515 						constraintStep *= loserMarks - winnerMarks;
516 					constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / losingConstraints;
517 					//constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) * winningConstraints;
518 					if (grammarHasChanged)
519 						*grammarHasChanged = true;
520 				}
521 				if (winnerMarks > loserMarks) {
522 					if (multiplyStepByNumberOfViolations)
523 						constraintStep *= winnerMarks - loserMarks;
524 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
525 					//constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * losingConstraints;
526 					if (grammarHasChanged)
527 						*grammarHasChanged = true;
528 				}
529 			}
530 		}
531 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL) {
532 		integer winningConstraints = 0, losingConstraints = 0;
533 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
534 			const integer winnerMarks = winner -> marks [icons];
535 			const integer loserMarks = loser -> marks [icons];
536 			if (loserMarks > 0)
537 				losingConstraints ++;
538 			if (winnerMarks > 0)
539 				winningConstraints ++;
540 		}
541 		if (winningConstraints != 0) for (integer icons = 1; icons <= my numberOfConstraints; icons ++)  {
542 			const OTConstraint constraint = & my constraints [icons];
543 			double constraintStep = step * constraint -> plasticity;
544 			const integer winnerMarks = winner -> marks [icons];
545 			const integer loserMarks = loser -> marks [icons];
546 			if (loserMarks > 0) {
547 				if (multiplyStepByNumberOfViolations)
548 					constraintStep *= loserMarks - winnerMarks;
549 				constraint -> ranking -= constraintStep * (1.0 + constraint -> ranking * my leak) / losingConstraints;
550 				if (grammarHasChanged)
551 					*grammarHasChanged = true;
552 			}
553 			if (winnerMarks > 0) {
554 				if (multiplyStepByNumberOfViolations)
555 					constraintStep *= winnerMarks - loserMarks;
556 				constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / winningConstraints;
557 				if (grammarHasChanged)
558 					*grammarHasChanged = true;
559 			}
560 		}
561 	} else if (updateRule == kOTGrammar_rerankingStrategy::EDCD || updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
562 		/*
563 			Determine the crucial winner mark.
564 		*/
565 		double pivotRanking;
566 		bool equivalent = true;
567 		integer icons = 1;
568 		for (; icons <= my numberOfConstraints; icons ++) {
569 			const integer winnerMarks = winner -> marks [my index [icons]];   // order is important, so indirect
570 			const integer loserMarks = loser -> marks [my index [icons]];
571 			if (loserMarks < winnerMarks)
572 				break;
573 			if (loserMarks > winnerMarks)
574 				equivalent = false;
575 		}
576 		if (icons > my numberOfConstraints) {   // completed the loop?
577 			if (warnIfStalled && ! equivalent)
578 				Melder_warning (U"Correct output is harmonically bounded (by having strict superset violations as compared to the learner's output)! EDCD stalls.\n"
579 						U"Correct output: ", loser -> string.get(), U"\nLearner's output: ", winner -> string.get());
580 			return;
581 		}
582 		/*
583 			Determine the stratum into which some constraints will be demoted.
584 		*/
585 		pivotRanking = my constraints [my index [icons]]. ranking;
586 		if (updateRule == kOTGrammar_rerankingStrategy::EDCD_WITH_VACATION) {
587 			integer numberOfConstraintsToDemote = 0;
588 			for (icons = 1; icons <= my numberOfConstraints; icons ++) {
589 				const integer winnerMarks = winner -> marks [icons];
590 				const integer loserMarks = loser -> marks [icons];
591 				if (loserMarks > winnerMarks) {
592 					OTConstraint constraint = & my constraints [icons];
593 					if (constraint -> ranking >= pivotRanking)
594 						numberOfConstraintsToDemote += 1;
595 				}
596 			}
597 			if (numberOfConstraintsToDemote > 0) {
598 				for (icons = 1; icons <= my numberOfConstraints; icons ++) {
599 					const OTConstraint constraint = & my constraints [icons];
600 					if (constraint -> ranking < pivotRanking) {
601 						constraint -> ranking -= numberOfConstraintsToDemote * step * constraint -> plasticity;
602 						if (grammarHasChanged)
603 							*grammarHasChanged = true;
604 					}
605 				}
606 			}
607 		}
608 		/*
609 			Demote all the uniquely violated constraints in the loser
610 			that have rankings not lower than the pivot.
611 		*/
612 		for (icons = 1; icons <= my numberOfConstraints; icons ++) {
613 			integer numberOfConstraintsDemoted = 0;
614 			const integer winnerMarks = winner -> marks [my index [icons]];   // for the vacation version, the order is important, so indirect
615 			const integer loserMarks = loser -> marks [my index [icons]];
616 			if (loserMarks > winnerMarks) {
617 				OTConstraint constraint = & my constraints [my index [icons]];
618 				double constraintStep = step * constraint -> plasticity;
619 				if (constraint -> ranking >= pivotRanking) {
620 					numberOfConstraintsDemoted += 1;
621 					constraint -> ranking = pivotRanking - numberOfConstraintsDemoted * constraintStep;   // this preserves the order of the demotees
622 					if (grammarHasChanged)
623 						*grammarHasChanged = true;
624 				}
625 			}
626 		}
627 	} else if (updateRule == kOTGrammar_rerankingStrategy::DEMOTION_ONLY) {
628 		/*
629 			Determine the crucial loser mark.
630 		*/
631 		integer crucialLoserMark;
632 		OTConstraint offendingConstraint;
633 		integer icons = 1;
634 		for (; icons <= my numberOfConstraints; icons ++) {
635 			const integer winnerMarks = winner -> marks [my index [icons]];   // order is important, so indirect
636 			const integer loserMarks = loser -> marks [my index [icons]];
637 			if (my constraints [my index [icons]]. tiedToTheRight)
638 				Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
639 			if (loserMarks < winnerMarks) {
640 				if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
641 					Melder_throw (U"Demotion-only learning step: Loser wins! Should never happen.");
642 				} else {
643 					// do nothing; the whole demotion-only idea does not really apply very well to non-OT decision strategies
644 				}
645 			}
646 			if (loserMarks > winnerMarks)
647 				break;
648 		}
649 		if (icons > my numberOfConstraints) {   // completed the loop?
650 			if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
651 				Melder_throw (U"(OTGrammar_step:) Loser equals correct candidate: loser \"",
652 						loser -> string.get(), U"\", winner \"", winner -> string.get(), U"\".");
653 			} else {
654 				// do nothing
655 			}
656 		} else {
657 			crucialLoserMark = icons;
658 			/*
659 				Demote the highest uniquely violated constraint in the loser.
660 			*/
661 			offendingConstraint = & my constraints [my index [crucialLoserMark]];
662 			const double constraintStep = step * offendingConstraint -> plasticity;
663 			offendingConstraint -> ranking -= constraintStep;
664 			if (grammarHasChanged)
665 				*grammarHasChanged = true;
666 		}
667 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN) {
668 		bool changed = false;
669 		integer numberOfUp = 0;
670 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
671 			const integer winnerMarks = winner -> marks [icons];
672 			const integer loserMarks = loser -> marks [icons];
673 			if (winnerMarks > loserMarks)
674 				numberOfUp ++;
675 		}
676 		if (numberOfUp > 0) {
677 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
678 				const OTConstraint constraint = & my constraints [icons];
679 				double constraintStep = step * constraint -> plasticity;
680 				const integer winnerMarks = winner -> marks [icons];
681 				const integer loserMarks = loser -> marks [icons];
682 				if (winnerMarks > loserMarks) {
683 					if (multiplyStepByNumberOfViolations)
684 						constraintStep *= winnerMarks - loserMarks;
685 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / numberOfUp;
686 				}
687 			}
688 			integer winnerMarks = 0, loserMarks = 0;
689 			integer icons = 1;
690 			for (; icons <= my numberOfConstraints; icons ++) {
691 				winnerMarks = winner -> marks [my index [icons]];   // order is important, so indirect
692 				loserMarks = loser -> marks [my index [icons]];
693 				if (my constraints [my index [icons]]. tiedToTheRight)
694 					Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
695 				if (loserMarks < winnerMarks) {
696 					if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
697 						Melder_throw (U"Demotion-only learning step: Loser wins! Should never happen.");
698 					} else {
699 						// do nothing; the whole demotion-only idea does not really apply very well to non-OT decision strategies
700 					}
701 				}
702 				if (loserMarks > winnerMarks)
703 					break;
704 			}
705 			if (icons > my numberOfConstraints) {   // completed the loop?
706 				if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
707 					Melder_throw (U"(OTGrammar_step:) Loser equals correct candidate: loser \"",
708 						loser -> string.get(), U"\", winner \"", winner -> string.get(), U"\".");
709 				} else {
710 					// do nothing
711 				}
712 			} else {
713 				const integer crucialLoserMark = icons;
714 				/*
715 					Demote the highest uniquely violated constraint in the loser.
716 				*/
717 				const OTConstraint offendingConstraint = & my constraints [my index [crucialLoserMark]];
718 				double constraintStep = step * offendingConstraint -> plasticity;
719 				if (multiplyStepByNumberOfViolations)
720 					constraintStep *= winnerMarks - loserMarks;
721 				offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
722 			}
723 		}
724 		if (grammarHasChanged)
725 			*grammarHasChanged = changed;
726 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGHEST_DOWN_2012) {
727 		bool changed = false;
728 		integer numberOfUp = 0;
729 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
730 			const integer winnerMarks = winner -> marks [icons];
731 			const integer loserMarks = loser -> marks [icons];
732 			if (winnerMarks > loserMarks)
733 				numberOfUp ++;
734 		}
735 		if (/*true ||*/ numberOfUp > 0) {
736 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
737 				const OTConstraint constraint = & my constraints [icons];
738 				double constraintStep = step * constraint -> plasticity;
739 				const integer winnerMarks = winner -> marks [icons];
740 				const integer loserMarks = loser -> marks [icons];
741 				if (winnerMarks > loserMarks) {
742 					if (multiplyStepByNumberOfViolations)
743 						constraintStep *= winnerMarks - loserMarks;
744 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) / (numberOfUp + 1);
745 				}
746 			}
747 			integer winnerMarks = 0, loserMarks = 0;
748 			integer icons = 1;
749 			for (; icons <= my numberOfConstraints; icons ++) {
750 				winnerMarks = winner -> marks [my index [icons]];   /* Order is important, so indirect. */
751 				loserMarks = loser -> marks [my index [icons]];
752 				if (my constraints [my index [icons]]. tiedToTheRight)
753 					Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
754 				if (loserMarks < winnerMarks) {
755 					if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
756 						Melder_throw (U"Demotion-only learning step: Loser wins! Should never happen.");
757 					} else {
758 						// do nothing; the whole demotion-only idea does not really apply very well to non-OT decision strategies
759 					}
760 				}
761 				if (loserMarks > winnerMarks)
762 					break;
763 			}
764 			if (icons > my numberOfConstraints) {   // completed the loop?
765 				if (my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
766 					Melder_throw (U"(OTGrammar_step:) Loser equals correct candidate: loser \"",
767 						loser -> string.get(), U"\", winner \"", winner -> string.get(), U"\".");
768 				} else {
769 					// do nothing
770 				}
771 			} else {
772 				const integer crucialLoserMark = icons;
773 				/*
774 					Demote the highest uniquely violated constraint in the loser.
775 				*/
776 				const OTConstraint offendingConstraint = & my constraints [my index [crucialLoserMark]];
777 				double constraintStep = step * offendingConstraint -> plasticity;
778 				if (multiplyStepByNumberOfViolations)
779 					constraintStep *= winnerMarks - loserMarks;
780 				offendingConstraint -> ranking -= /*numberOfUp **/ constraintStep * (1.0 - offendingConstraint -> ranking * my leak);
781 			}
782 		}
783 		if (grammarHasChanged)
784 			*grammarHasChanged = changed;
785 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN) {
786 		integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
787 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
788 			const integer winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
789 			const integer loserMarks = loser -> marks [my index [icons]];
790 			if (loserMarks < winnerMarks) {
791 				numberOfUp ++;
792 			} else if (loserMarks > winnerMarks) {
793 				if (numberOfUp == 0) {
794 					numberOfDown ++;
795 					lowestDemotableConstraint = icons;
796 				}
797 			}
798 		}
799 		if (numberOfUp > 0) {
800 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
801 				const integer constraintIndex = my index [icons];
802 				const OTConstraint constraint = & my constraints [constraintIndex];
803 				double constraintStep = step * constraint -> plasticity;
804 				const integer winnerMarks = winner -> marks [constraintIndex];   // the order is important, therefore indirect
805 				const integer loserMarks = loser -> marks [constraintIndex];
806 				if (my constraints [constraintIndex]. tiedToTheRight)
807 					Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
808 				if (loserMarks < winnerMarks) {
809 					if (multiplyStepByNumberOfViolations)
810 						constraintStep *= winnerMarks - loserMarks;
811 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 0.0);
812 				} else if (loserMarks > winnerMarks) {
813 					if (icons <= lowestDemotableConstraint) {
814 						if (multiplyStepByNumberOfViolations)
815 							constraintStep *= loserMarks - winnerMarks;
816 						constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
817 					}
818 				}
819 			}
820 			if (grammarHasChanged)
821 				*grammarHasChanged = true;
822 		}
823 	} else if (updateRule == kOTGrammar_rerankingStrategy::WEIGHTED_ALL_UP_HIGH_DOWN_2012) {
824 		integer numberOfDown = 0, numberOfUp = 0, lowestDemotableConstraint = 0;
825 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
826 			const integer winnerMarks = winner -> marks [my index [icons]];   // the order is important, therefore indirect
827 			const integer loserMarks = loser -> marks [my index [icons]];
828 			if (loserMarks < winnerMarks) {
829 				numberOfUp ++;
830 			} else if (loserMarks > winnerMarks) {
831 				if (numberOfUp == 0) {
832 					numberOfDown ++;
833 					lowestDemotableConstraint = icons;
834 				}
835 			}
836 		}
837 		if (numberOfUp > 0) {
838 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
839 				const integer constraintIndex = my index [icons];
840 				const OTConstraint constraint = & my constraints [constraintIndex];
841 				double constraintStep = step * constraint -> plasticity;
842 				const integer winnerMarks = winner -> marks [constraintIndex];   // the order is important, therefore indirect
843 				const integer loserMarks = loser -> marks [constraintIndex];
844 				if (my constraints [constraintIndex]. tiedToTheRight)
845 					Melder_throw (U"Demotion-only learning cannot handle tied constraints.");
846 				if (loserMarks < winnerMarks) {
847 					if (multiplyStepByNumberOfViolations)
848 						constraintStep *= winnerMarks - loserMarks;
849 					constraint -> ranking += constraintStep * (1.0 - constraint -> ranking * my leak) * numberOfDown / (numberOfUp + 1.0);
850 				} else if (loserMarks > winnerMarks) {
851 					if (icons <= lowestDemotableConstraint) {
852 						if (multiplyStepByNumberOfViolations)
853 							constraintStep *= loserMarks - winnerMarks;
854 						constraint -> ranking -= constraintStep * (1.0 - constraint -> ranking * my leak);
855 					}
856 				}
857 			}
858 			if (grammarHasChanged)
859 				*grammarHasChanged = true;
860 		}
861 	}
862 }
863 
864 int OTMulti_learnOne (OTMulti me, conststring32 form1, conststring32 form2,
865 	enum kOTGrammar_rerankingStrategy updateRule, int direction, double plasticity, double relativePlasticityNoise)
866 {
867 	integer iloser = OTMulti_getWinner (me, form1, form2);
868 	if (direction & OTMulti_LEARN_FORWARD) {
869 		if (Melder_debug == 47) OTMulti_newDisharmonies (me, 2.0);
870 		integer iwinner = OTMulti_getWinner (me, form1, U"");
871 		if (Melder_debug != 47 || ! OTMulti_candidateMatches (me, iwinner, form2, U""))
872 			OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
873 	}
874 	if (direction & OTMulti_LEARN_BACKWARD) {
875 		if (Melder_debug == 47) OTMulti_newDisharmonies (me, 2.0);
876 		integer iwinner = OTMulti_getWinner (me, form2, U"");
877 		if (Melder_debug != 47 || ! OTMulti_candidateMatches (me, iwinner, form1, U""))
878 			OTMulti_modifyRankings (me, iwinner, iloser, updateRule, plasticity, relativePlasticityNoise);
879 	}
880 	return 1;
881 }
882 
883 static autoTable OTMulti_createHistory (OTMulti me, integer storeHistoryEvery, integer numberOfData)
884 {
885 	try {
886 		integer numberOfSamplingPoints = numberOfData / storeHistoryEvery;   // e.g. 0, 20, 40, ...
887 		autoTable thee = Table_createWithoutColumnNames (1 + numberOfSamplingPoints, 3 + my numberOfConstraints);
888 		Table_setColumnLabel (thee.get(), 1, U"Datum");
889 		Table_setColumnLabel (thee.get(), 2, U"Form1");
890 		Table_setColumnLabel (thee.get(), 3, U"Form2");
891 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
892 			Table_setColumnLabel (thee.get(), 3 + icons, my constraints [icons]. name.get());
893 		Table_setNumericValue (thee.get(), 1, 1, 0);
894 		Table_setStringValue (thee.get(), 1, 2, U"(initial)");
895 		Table_setStringValue (thee.get(), 1, 3, U"(initial)");
896 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
897 			Table_setNumericValue (thee.get(), 1, 3 + icons, my constraints [icons]. ranking);
898 		return thee;
899 	} catch (MelderError) {
900 		Melder_throw (me, U": history not created.");
901 	}
902 }
903 
904 static void OTMulti_updateHistory (OTMulti me, Table thee, integer storeHistoryEvery, integer idatum, conststring32 form1, conststring32 form2)
905 {
906 	try {
907 		if (idatum % storeHistoryEvery == 0) {
908 			integer irow = 1 + idatum / storeHistoryEvery;
909 			Table_setNumericValue (thee, irow, 1, idatum);
910 			Table_setStringValue (thee, irow, 2, form1);
911 			Table_setStringValue (thee, irow, 3, form2);
912 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
913 				Table_setNumericValue (thee, irow, 3 + icons, my constraints [icons]. ranking);
914 		}
915 	} catch (MelderError) {
916 		Melder_throw (me, U": history not updated.");
917 	}
918 }
919 
920 
921 void OTMulti_PairDistribution_learn (OTMulti me, PairDistribution thee, double evaluationNoise, enum kOTGrammar_rerankingStrategy updateRule, int direction,
922 	double initialPlasticity, integer replicationsPerPlasticity, double plasticityDecrement,
923 	integer numberOfPlasticities, double relativePlasticityNoise, integer storeHistoryEvery, autoTable *history_out)
924 {
925 	integer idatum = 0, numberOfData = numberOfPlasticities * replicationsPerPlasticity;
926 	try {
927 		double plasticity = initialPlasticity;
928 		autoMelderMonitor monitor (U"Learning from partial pairs...");
929 		autoTable history;
930 		if (storeHistoryEvery)
931 			history = OTMulti_createHistory (me, storeHistoryEvery, numberOfData);
932 		for (integer iplasticity = 1; iplasticity <= numberOfPlasticities; iplasticity ++) {
933 			for (integer ireplication = 1; ireplication <= replicationsPerPlasticity; ireplication ++) {
934 				conststring32 form1, form2;
935 				PairDistribution_peekPair (thee, & form1, & form2);
936 				++ idatum;
937 				if (monitor.graphics() && idatum % (numberOfData / 400 + 1) == 0) {
938 					integer numberOfDrawnConstraints = my numberOfConstraints < 14 ? my numberOfConstraints : 14;
939 					if (numberOfDrawnConstraints > 0) {
940 						longdouble sumOfRankings = 0.0;
941 						for (integer icons = 1; icons <= numberOfDrawnConstraints; icons ++)
942 							sumOfRankings += my constraints [icons]. ranking;
943 						double meanRanking = double (sumOfRankings) / numberOfDrawnConstraints;
944 						Graphics_beginMovieFrame (monitor.graphics(), nullptr);
945 						Graphics_setWindow (monitor.graphics(), 0.0, numberOfData, meanRanking - 50.0, meanRanking + 50.0);
946 						for (integer icons = 1; icons <= numberOfDrawnConstraints; icons ++) {
947 							Graphics_setGrey (monitor.graphics(), (double) icons / numberOfDrawnConstraints);
948 							Graphics_line (monitor.graphics(),
949 									idatum, my constraints [icons]. ranking,
950 									idatum, my constraints [icons]. ranking + 1);
951 						}
952 						Graphics_endMovieFrame (monitor.graphics(), 0.0);
953 					}
954 				}
955 				try {
956 					Melder_monitor ((double) idatum / numberOfData,
957 						U"Processing partial pair ", idatum, U" out of ", numberOfData,
958 						U":\n      ", form1, U"     ", form2);
959 				} catch (MelderError) {
960 					if (history_out)
961 						*history_out = history.move();   // so that we can inspect
962 					throw;
963 				}
964 				OTMulti_newDisharmonies (me, evaluationNoise);
965 				try {
966 					OTMulti_learnOne (me, form1, form2, updateRule, direction, plasticity, relativePlasticityNoise);
967 				} catch (MelderError) {
968 					if (history)
969 						OTMulti_updateHistory (me, history.get(), storeHistoryEvery, idatum, form1, form2);
970 					throw;
971 				}
972 				if (history)
973 					OTMulti_updateHistory (me, history.get(), storeHistoryEvery, idatum, form1, form2);
974 			}
975 			plasticity *= plasticityDecrement;
976 		}
977 		if (history_out)
978 			*history_out = history.move();
979 	} catch (MelderError) {
980 		if (idatum > 1)
981 			Melder_appendError (U"Only ", idatum - 1, U" input-output pairs out of ", numberOfData, U" were processed.");
982 		Melder_throw (me, U" & ", thee, U": learning from partial pairs not completed.");
983 	}
984 }
985 
986 static integer OTMulti_crucialCell (OTMulti me, integer icand, integer iwinner, integer numberOfOptimalCandidates, conststring32 form1, conststring32 form2)
987 {
988 	if (my numberOfCandidates < 2) return 0;   // if there is only one candidate, all cells can be greyed
989 	if (OTMulti_compareCandidates (me, icand, iwinner) == 0) {   // candidate equally good as winner?
990 		if (numberOfOptimalCandidates > 1) {
991 			/* All cells are important. */
992 		} else {
993 			integer secondBest = 0;
994 			for (integer jcand = 1; jcand <= my numberOfCandidates; jcand ++) {
995 				if (OTMulti_candidateMatches (me, jcand, form1, form2) && OTMulti_compareCandidates (me, jcand, iwinner) != 0) {   // a non-optimal candidate?
996 					if (secondBest == 0) {
997 						secondBest = jcand;   // first guess
998 					} else if (OTMulti_compareCandidates (me, jcand, secondBest) < 0) {
999 						secondBest = jcand;   // better guess
1000 					}
1001 				}
1002 			}
1003 			if (secondBest == 0) return 0;   // if all candidates are equally good, all cells can be greyed
1004 			return OTMulti_crucialCell (me, secondBest, iwinner, 1, form1, form2);
1005 		}
1006 	} else {
1007 		const constINTVEC candidateMarks = my candidates [icand]. marks.get();
1008 		const constINTVEC winnerMarks = my candidates [iwinner]. marks.get();
1009 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1010 			const integer numberOfCandidateMarks = candidateMarks [my index [icons]];
1011 			const integer numberOfWinnerMarks = winnerMarks [my index [icons]];
1012 			if (numberOfCandidateMarks > numberOfWinnerMarks)
1013 				return icons;
1014 		}
1015 	}
1016 	return my numberOfConstraints;   // nothing grey
1017 }
1018 
1019 static double OTMulti_constraintWidth (Graphics g, OTConstraint constraint, bool showDisharmony) {
1020 	char32 text [100], *newLine;
1021 	double maximumWidth = ( showDisharmony ? 0.8 * Graphics_textWidth_ps (g, Melder_fixed (constraint -> disharmony, 1), true) : 0.0 ),
1022 		firstWidth, secondWidth;
1023 	str32cpy (text, constraint -> name.get());
1024 	newLine = str32chr (text, U'\n');
1025 	if (newLine) {
1026 		*newLine = U'\0';
1027 		firstWidth = Graphics_textWidth_ps (g, text, true);
1028 		if (firstWidth > maximumWidth)
1029 			maximumWidth = firstWidth;
1030 		secondWidth = Graphics_textWidth_ps (g, newLine + 1, true);
1031 		if (secondWidth > maximumWidth)
1032 			maximumWidth = secondWidth;
1033 		return maximumWidth;
1034 	}
1035 	firstWidth = Graphics_textWidth_ps (g, text, true);
1036 	if (firstWidth > maximumWidth)
1037 		maximumWidth = firstWidth;
1038 	return maximumWidth;
1039 }
1040 
1041 void OTMulti_drawTableau (OTMulti me, Graphics g, conststring32 form1, conststring32 form2, bool vertical, bool showDisharmonies) {
1042 	integer winner, winner1 = 0, winner2 = 0;
1043 	double x, y, fontSize = Graphics_inqFontSize (g);
1044 	MelderColour colour = Graphics_inqColour (g);
1045 	char32 text [200];
1046 	bool bidirectional = form1 [0] != U'\0' && form2 [0] != U'\0';
1047 	try {
1048 		winner = OTMulti_getWinner (me, form1, form2);
1049 	} catch (MelderError) {
1050 		Melder_clearError ();
1051 		Graphics_setWindow (g, 0.0, 1.0, 0.0, 1.0);
1052 		Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
1053 		Graphics_rectangle (g, 0, 1, 0, 1);
1054 		Graphics_text (g, 0.0, 0.5, U"(no matching candidates)");
1055 		return;
1056 	}
1057 
1058 	if (bidirectional) {
1059 		winner1 = OTMulti_getWinner (me, form1, U"");
1060 		winner2 = OTMulti_getWinner (me, form2, U"");
1061 	}
1062 	Graphics_setWindow (g, 0.0, 1.0, 0.0, 1.0);
1063 	const double margin = Graphics_dxMMtoWC (g, 1.0);
1064 	const double fingerWidth = Graphics_dxMMtoWC (g, 7.0) * fontSize / 12.0;
1065 	const double doubleLineDx = Graphics_dxMMtoWC (g, 0.9);
1066 	const double doubleLineDy = Graphics_dyMMtoWC (g, 0.9);
1067 	const double rowHeight = Graphics_dyMMtoWC (g, 1.5 * fontSize * 25.4 / 72);
1068 	const double descent = rowHeight * 0.5;
1069 	const double worldAspectRatio = Graphics_dyMMtoWC (g, 1.0) / Graphics_dxMMtoWC (g, 1.0);   // because Graphics_textWidth measures in the x direction only
1070 	/*
1071 	 * Compute height of header row.
1072 	 */
1073 	double headerHeight;
1074 	if (vertical) {
1075 		headerHeight = 0.0;
1076 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1077 			const OTConstraint constraint = & my constraints [icons];
1078 			const double constraintTextWidth = Graphics_textWidth (g, constraint -> name.get());
1079 			if (constraintTextWidth > headerHeight)
1080 				headerHeight = constraintTextWidth;
1081 		}
1082 		headerHeight += margin * 2;
1083 		headerHeight *= worldAspectRatio;
1084 	} else {
1085 		headerHeight = rowHeight;
1086 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1087 			const OTConstraint constraint = & my constraints [icons];
1088 			if (str32chr (constraint -> name.get(), U'\n')) {
1089 				headerHeight += 0.7 * rowHeight;
1090 				break;
1091 			}
1092 		}
1093 	}
1094 	/*
1095 		Compute longest candidate string.
1096 		Also count the number of optimal candidates (if there are more than one, the fingers will be drawn in red).
1097 	*/
1098 	double candWidth = Graphics_textWidth_ps (g, form1, true) + Graphics_textWidth_ps (g, form2, true);
1099 	integer numberOfMatchingCandidates = 0;
1100 	integer numberOfOptimalCandidates = 0;
1101 	integer numberOfOptimalCandidates1 = 0;
1102 	integer numberOfOptimalCandidates2 = 0;
1103 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++) {
1104 		if ((form1 [0] != U'\0' && OTMulti_candidateMatches (me, icand, form1, U"")) ||
1105 		    (form2 [0] != U'\0' && OTMulti_candidateMatches (me, icand, form2, U"")) ||
1106 		    (form1 [0] == U'\0' && form2 [0] == U'\0'))
1107 		{
1108 			const double width = Graphics_textWidth_ps (g, my candidates [icand]. string.get(), true);
1109 			if (width > candWidth)
1110 				candWidth = width;
1111 			numberOfMatchingCandidates ++;
1112 			if (OTMulti_compareCandidates (me, icand, winner) == 0)
1113 				numberOfOptimalCandidates ++;
1114 			if (winner1 != 0 && OTMulti_compareCandidates (me, icand, winner1) == 0)
1115 				numberOfOptimalCandidates1 ++;
1116 			if (winner2 != 0 && OTMulti_compareCandidates (me, icand, winner2) == 0)
1117 				numberOfOptimalCandidates2 ++;
1118 		}
1119 	}
1120 	candWidth += fingerWidth * (bidirectional ? 3 : 1) + margin * 3;
1121 	/*
1122 		Compute tableau width.
1123 	*/
1124 	longdouble tableauWidth = candWidth + doubleLineDx;
1125 	if (vertical) {
1126 		tableauWidth += rowHeight * my numberOfConstraints / worldAspectRatio;
1127 	} else {
1128 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1129 			const OTConstraint constraint = & my constraints [icons];
1130 			tableauWidth += OTMulti_constraintWidth (g, constraint, showDisharmonies);
1131 		}
1132 		tableauWidth += margin * 2 * my numberOfConstraints;
1133 	}
1134 	/*
1135 		Draw box.
1136 	*/
1137 	x = doubleLineDx;   // left side of tableau
1138 	y = 1.0 - doubleLineDy;
1139 	if (showDisharmonies)
1140 		y -= 0.6 * rowHeight;
1141 	Graphics_rectangle (g, x, x + tableauWidth,
1142 			y - headerHeight - numberOfMatchingCandidates * rowHeight - doubleLineDy, y);
1143 	/*
1144 		Draw input.
1145 	*/
1146 	y -= headerHeight;
1147 	Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
1148 	Graphics_text (g, x + 0.5 * candWidth, y + 0.5 * headerHeight,   form1, form2);
1149 	Graphics_rectangle (g, x, x + candWidth, y, y + headerHeight);
1150 	/*
1151 		Draw constraint names.
1152 	*/
1153 	x += candWidth + doubleLineDx;
1154 	if (vertical)
1155 		Graphics_setTextRotation (g, 90.0);
1156 	for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1157 		const OTConstraint constraint = & my constraints [my index [icons]];
1158 		const double width = ( vertical ? rowHeight / worldAspectRatio : OTMulti_constraintWidth (g, constraint, showDisharmonies) + margin * 2 );
1159 		if (str32chr (constraint -> name.get(), U'\n')) {
1160 			char32 *newLine;
1161 			Melder_sprint (text,200, constraint -> name.get());
1162 			newLine = str32chr (text, U'\n');
1163 			*newLine = U'\0';
1164 			Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_TOP);
1165 			Graphics_text (g, x + 0.5 * width, y + headerHeight, text);
1166 			Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
1167 			Graphics_text (g, x + 0.5 * width, y, newLine + 1);
1168 		} else if (vertical) {
1169 			Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
1170 			Graphics_text (g, x + 0.5 * width, y + margin, constraint -> name.get());
1171 		} else {
1172 			Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
1173 			Graphics_text (g, x + 0.5 * width, y + 0.5 * headerHeight, constraint -> name.get());
1174 		}
1175 		if (showDisharmonies) {
1176 			Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_BOTTOM);
1177 			Graphics_setFontSize (g, 0.8 * fontSize);
1178 			Graphics_text (g, x + 0.5 * width, y + headerHeight, Melder_fixed (constraint -> disharmony, 1));
1179 			Graphics_setFontSize (g, fontSize);
1180 		}
1181 		Graphics_line (g, x, y, x, y + headerHeight);
1182 		Graphics_line (g, x, y, x + width, y);
1183 		x += width;
1184 	}
1185 	if (vertical)
1186 		Graphics_setTextRotation (g, 0.0);
1187 	/*
1188 		Draw candidates.
1189 	*/
1190 	y -= doubleLineDy;
1191 	for (integer icand = 1; icand <= my numberOfCandidates; icand ++)
1192 		if ((form1 [0] != U'\0' && OTMulti_candidateMatches (me, icand, form1, U"")) ||
1193 		    (form2 [0] != U'\0' && OTMulti_candidateMatches (me, icand, form2, U"")) ||
1194 		    (form1 [0] == U'\0' && form2 [0] == U'\0'))
1195 	{
1196 		const integer crucialCell = OTMulti_crucialCell (me, icand, winner, numberOfOptimalCandidates, form1, form2);
1197 		const bool candidateIsOptimal = ( OTMulti_compareCandidates (me, icand, winner) == 0 );
1198 		const bool candidateIsOptimal1 = ( winner1 != 0 && OTMulti_compareCandidates (me, icand, winner1) == 0 );
1199 		const bool candidateIsOptimal2 = ( winner2 != 0 && OTMulti_compareCandidates (me, icand, winner2) == 0 );
1200 		/*
1201 			Draw candidate transcription.
1202 		*/
1203 		x = doubleLineDx;
1204 		y -= rowHeight;
1205 		Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
1206 		Graphics_text (g, x + candWidth - margin, y + descent, my candidates [icand]. string.get());
1207 		if (candidateIsOptimal) {
1208 			Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
1209 			Graphics_setFontSize (g, ( bidirectional ? 1.2 : 1.5 ) * fontSize);
1210 			if (numberOfOptimalCandidates > 1)
1211 				Graphics_setColour (g, Melder_RED);
1212 			Graphics_text (g, x + margin, y + descent - Graphics_dyMMtoWC (g, 0.5) * fontSize / 12.0, bidirectional ? U"\\Vr" : U"\\pf");
1213 			Graphics_setColour (g, colour);
1214 			Graphics_setFontSize (g, fontSize);
1215 		}
1216 		if (candidateIsOptimal1) {
1217 			Graphics_setTextAlignment (g, Graphics_LEFT, Graphics_HALF);
1218 			Graphics_setFontSize (g, 1.5 * fontSize);
1219 			if (numberOfOptimalCandidates1 > 1)
1220 				Graphics_setColour (g, Melder_RED);
1221 			Graphics_text (g, x + margin + fingerWidth, y + descent - Graphics_dyMMtoWC (g, 0.5) * fontSize / 12.0, U"\\pf");
1222 			Graphics_setColour (g, colour);
1223 			Graphics_setFontSize (g, fontSize);
1224 		}
1225 		if (candidateIsOptimal2) {
1226 			Graphics_setTextAlignment (g, Graphics_RIGHT, Graphics_HALF);
1227 			Graphics_setFontSize (g, 1.5 * fontSize);
1228 			if (numberOfOptimalCandidates2 > 1)
1229 				Graphics_setColour (g, Melder_RED);
1230 			Graphics_setTextRotation (g, 180);
1231 			Graphics_text (g, x + margin + fingerWidth * 2, y + descent - Graphics_dyMMtoWC (g, 0.0) * fontSize / 12.0, U"\\pf");
1232 			Graphics_setTextRotation (g, 0);
1233 			Graphics_setColour (g, colour);
1234 			Graphics_setFontSize (g, fontSize);
1235 		}
1236 		Graphics_rectangle (g, x, x + candWidth, y, y + rowHeight);
1237 		/*
1238 			Draw grey cell backgrounds.
1239 		*/
1240 		if (! bidirectional && my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY) {
1241 			x = candWidth + 2 * doubleLineDx;
1242 			Graphics_setGrey (g, 0.9);
1243 			for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1244 				integer index = my index [icons];
1245 				OTConstraint constraint = & my constraints [index];
1246 				const double width = ( vertical ? rowHeight / worldAspectRatio : OTMulti_constraintWidth (g, constraint, showDisharmonies) + margin * 2 );
1247 				if (icons > crucialCell)
1248 					Graphics_fillRectangle (g, x, x + width, y, y + rowHeight);
1249 				x += width;
1250 			}
1251 			Graphics_setColour (g, colour);
1252 		}
1253 		/*
1254 			Draw cell marks.
1255 		*/
1256 		x = candWidth + 2 * doubleLineDx;
1257 		Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
1258 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1259 			const integer index = my index [icons];
1260 			const OTConstraint constraint = & my constraints [index];
1261 			const double width = ( vertical ? rowHeight / worldAspectRatio : OTMulti_constraintWidth (g, constraint, showDisharmonies) + margin * 2 );
1262 			char32 markString [40];
1263 			markString [0] = U'\0';
1264 			if (bidirectional && my candidates [icand]. marks [index] > 0) {
1265 				if ((candidateIsOptimal1 || candidateIsOptimal2) && ! candidateIsOptimal)
1266 					str32cat (markString, U"\\<-");
1267 			}
1268 			if (bidirectional && my candidates [icand]. marks [index] < 0) {
1269 				if (candidateIsOptimal && ! candidateIsOptimal1)
1270 					str32cat (markString, U"\\<-");
1271 				if (candidateIsOptimal && ! candidateIsOptimal2)
1272 					str32cat (markString, U"\\<-");
1273 			}
1274 			/*
1275 				An exclamation mark can be drawn in this cell only if both of the following conditions are met:
1276 				1. the candidate is not optimal;
1277 				2. this is the crucial cell, i.e. the cells after it are drawn in grey.
1278 			*/
1279 			if (! bidirectional && icons == crucialCell && ! candidateIsOptimal &&
1280 			    my decisionStrategy == kOTGrammar_decisionStrategy::OPTIMALITY_THEORY)
1281 			{
1282 				const integer winnerMarks = my candidates [winner]. marks [index];
1283 				if (winnerMarks + 1 > 5) {
1284 					str32cat (markString, Melder_integer (winnerMarks + 1));
1285 				} else {
1286 					for (integer imark = 1; imark <= winnerMarks + 1; imark ++)
1287 						str32cat (markString, U"*");
1288 				}
1289 				for (integer imark = my candidates [icand]. marks [index]; imark < 0; imark ++)
1290 					str32cat (markString, U"+");
1291 				str32cat (markString, U"!");
1292 				if (my candidates [icand]. marks [index] - (winnerMarks + 2) + 1 > 5) {
1293 					str32cat (markString, Melder_integer (my candidates [icand]. marks [index] - (winnerMarks + 2) + 1));
1294 				} else {
1295 					for (integer imark = winnerMarks + 2; imark <= my candidates [icand]. marks [index]; imark ++)
1296 						str32cat (markString, U"*");
1297 				}
1298 			} else {
1299 				if (my candidates [icand]. marks [index] > 5) {
1300 					str32cat (markString, Melder_integer (my candidates [icand]. marks [index]));
1301 				} else {
1302 					for (integer imark = 1; imark <= my candidates [icand]. marks [index]; imark ++)
1303 						str32cat (markString, U"*");
1304 					for (integer imark = my candidates [icand]. marks [index]; imark < 0; imark ++)
1305 						str32cat (markString, U"+");
1306 				}
1307 			}
1308 			if (bidirectional && my candidates [icand]. marks [index] > 0) {
1309 				if (candidateIsOptimal && ! candidateIsOptimal1)
1310 					str32cat (markString, U"\\->");
1311 				if (candidateIsOptimal && ! candidateIsOptimal2)
1312 					str32cat (markString, U"\\->");
1313 			}
1314 			if (bidirectional && my candidates [icand]. marks [index] < 0) {
1315 				if ((candidateIsOptimal1 || candidateIsOptimal2) && ! candidateIsOptimal)
1316 					str32cat (markString, U"\\->");
1317 			}
1318 			Graphics_text (g, x + 0.5 * width, y + descent, markString);
1319 			Graphics_setColour (g, colour);
1320 			Graphics_line (g, x, y, x, y + rowHeight);
1321 			Graphics_line (g, x, y + rowHeight, x + width, y + rowHeight);
1322 			x += width;
1323 		}
1324 	}
1325 	/*
1326 		Draw box.
1327 	*/
1328 	x = doubleLineDx;   // left side of tableau
1329 	y = 1.0 - doubleLineDy;
1330 	if (showDisharmonies)
1331 		y -= 0.6 * rowHeight;
1332 	Graphics_rectangle (g, x, x + tableauWidth,
1333 		y - headerHeight - numberOfMatchingCandidates * rowHeight - doubleLineDy, y);
1334 }
1335 
1336 void OTMulti_reset (OTMulti me, double ranking) {
1337 	for (integer icons = 1; icons <= my numberOfConstraints; icons ++) {
1338 		const OTConstraint constraint = & my constraints [icons];
1339 		constraint -> disharmony = constraint -> ranking = ranking;
1340 	}
1341 	OTMulti_sort (me);
1342 }
1343 
1344 void OTMulti_setRanking (OTMulti me, integer constraint, double ranking, double disharmony) {
1345 	try {
1346 		Melder_require (constraint >= 1,
1347 			U"The constraint number should be positive, not ", constraint, U".");
1348 		Melder_require (constraint <= my numberOfConstraints,
1349 			U"Constraint ", constraint, U" does not exist (there are only ", my numberOfConstraints, U" constraints).");
1350 		my constraints [constraint]. ranking = ranking;
1351 		my constraints [constraint]. disharmony = disharmony;
1352 		OTMulti_sort (me);
1353 	} catch (MelderError) {
1354 		Melder_throw (me, U": ranking not set.");
1355 	}
1356 }
1357 
1358 void OTMulti_setConstraintPlasticity (OTMulti me, integer constraint, double plasticity) {
1359 	try {
1360 		Melder_require (constraint >= 1,
1361 			U"The constraint number should be positive, not ", constraint, U".");
1362 		Melder_require (constraint <= my numberOfConstraints,
1363 			U"Constraint ", constraint, U" does not exist (there are only ", my numberOfConstraints, U" constraints).");
1364 		my constraints [constraint]. plasticity = plasticity;
1365 	} catch (MelderError) {
1366 		Melder_throw (me, U": constraint plasticity not set.");
1367 	}
1368 }
1369 
1370 void OTMulti_removeConstraint (OTMulti me, conststring32 constraintName) {
1371 	try {
1372 		if (my numberOfConstraints <= 1)
1373 			Melder_throw (me, U": cannot remove last constraint.");
1374 		integer constraintToBeRemoved = OTMulti_getConstraintIndexFromName (me, constraintName);
1375 		if (constraintToBeRemoved == 0)
1376 			Melder_throw (U"No constraint \"", constraintName, U"\".");
1377 		/*
1378 			Remove the constraint while reusing the memory space.
1379 		*/
1380 		my constraints [constraintToBeRemoved]. destroy ();
1381 		my constraints. remove (constraintToBeRemoved);
1382 		my numberOfConstraints -= 1;   // maintain invariant
1383 		Melder_assert (my numberOfConstraints == my constraints.size);
1384 		/*
1385 			Shift tableau rows.
1386 		*/
1387 		for (integer icand = 1; icand <= my numberOfCandidates; icand ++) {
1388 			const OTCandidate candidate = & my candidates [icand];
1389 			candidate -> marks. remove (constraintToBeRemoved);
1390 			candidate -> numberOfConstraints -= 1;   // maintain invariant
1391 			Melder_assert (candidate -> numberOfConstraints == candidate -> marks.size);
1392 		}
1393 		/*
1394 			Rebuild index.
1395 		*/
1396 		my index. resize (my numberOfConstraints);
1397 		for (integer icons = 1; icons <= my numberOfConstraints; icons ++)
1398 			my index [icons] = icons;
1399 		OTMulti_sort (me);
1400 	} catch (MelderError) {
1401 		Melder_throw (me, U": constraint not removed.");
1402 	}
1403 }
1404 
1405 autostring32 OTMulti_generateOptimalForm (OTMulti me, conststring32 form1, conststring32 form2, double evaluationNoise) {
1406 	try {
1407 		OTMulti_newDisharmonies (me, evaluationNoise);
1408 		integer winner = OTMulti_getWinner (me, form1, form2);
1409 		return Melder_dup (my candidates [winner]. string.get());
1410 	} catch (MelderError) {
1411 		Melder_throw (me, U": optimal form not generated.");
1412 	}
1413 }
1414 
1415 autoStrings OTMulti_Strings_generateOptimalForms (OTMulti me, Strings thee, double evaluationNoise) {
1416 	try {
1417 		autoStrings outputs = Thing_new (Strings);
1418 		integer n = thy numberOfStrings;
1419 		outputs -> numberOfStrings = n;
1420 		outputs -> strings = autoSTRVEC (n);
1421 		for (integer i = 1; i <= n; i ++)
1422 			outputs -> strings [i] = OTMulti_generateOptimalForm (me, thy strings [i].get(), U"", evaluationNoise);
1423 		return outputs;
1424 	} catch (MelderError) {
1425 		Melder_throw (me, U" & ", thee, U": optimal forms not generated.");
1426 	}
1427 }
1428 
1429 autoStrings OTMulti_generateOptimalForms (OTMulti me, conststring32 form1, conststring32 form2, integer numberOfTrials, double evaluationNoise) {
1430 	try {
1431 		autoStrings outputs = Thing_new (Strings);
1432 		outputs -> numberOfStrings = numberOfTrials;
1433 		outputs -> strings = autoSTRVEC (numberOfTrials);
1434 		for (integer i = 1; i <= numberOfTrials; i ++)
1435 			outputs -> strings [i] = OTMulti_generateOptimalForm (me, form1, form2, evaluationNoise);
1436 		return outputs;
1437 	} catch (MelderError) {
1438 		Melder_throw (me, U": optimal forms not generated.");
1439 	}
1440 }
1441 
1442 autoDistributions OTMulti_to_Distribution (OTMulti me, conststring32 form1, conststring32 form2,
1443 	integer numberOfTrials, double evaluationNoise)
1444 {
1445 	try {
1446 		integer totalNumberOfOutputs = 0, iout = 0;
1447 		/*
1448 			Count the total number of outputs.
1449 		*/
1450 		for (integer icand = 1; icand <= my numberOfCandidates; icand ++)
1451 			if (OTMulti_candidateMatches (me, icand, form1, form2))
1452 				totalNumberOfOutputs ++;
1453 		/*
1454 			Create the distribution. One row for every output form.
1455 		*/
1456 		autoDistributions thee = Distributions_create (totalNumberOfOutputs, 1);
1457 		autoINTVEC index = raw_INTVEC (my numberOfCandidates);
1458 		/*
1459 			Set the row labels to the output strings.
1460 		*/
1461 		iout = 0;
1462 		for (integer icand = 1; icand <= my numberOfCandidates; icand ++) {
1463 			if (OTMulti_candidateMatches (me, icand, form1, form2)) {
1464 				thy rowLabels [++ iout] = Melder_dup (my candidates [icand]. string.get());
1465 				index [icand] = iout;
1466 			}
1467 		}
1468 		/*
1469 			Compute a number of outputs and store the results.
1470 		*/
1471 		for (integer itrial = 1; itrial <= numberOfTrials; itrial ++) {
1472 			OTMulti_newDisharmonies (me, evaluationNoise);
1473 			integer iwinner = OTMulti_getWinner (me, form1, form2);
1474 			thy data [index [iwinner]] [1] += 1;
1475 		}
1476 		return thee;
1477 	} catch (MelderError) {
1478 		Melder_throw (me, U": distribution not computed.");
1479 	}
1480 }
1481 
1482 /* End of file OTMulti.cpp */
1483