source: trunk/src/org/apollo/meldex/DynamicProgrammingAlgorithm.java

Last change on this file was 315, checked in by bjn8, 16 years ago

Apollo spin-off added

  • Property svn:executable set to *
File size: 7.4 KB
Line 
1package org.apollo.meldex;
2
3/**
4 * This abstract class implements all of the mechanics involved in performing a match using an
5 * algorithm involving dynamic programming. Subclasses need to only implement a weighting
6 * function for comparing two melody events. Subclasses can also easily implement special
7 * functionality above the replacement/deletion/insertion that is standard.
8 *
9 * !! Can do matching at start better?? !!
10 */
11
12public abstract class DynamicProgrammingAlgorithm
13{
14 public static final int MATCH_ANYWHERE = 1;
15 public static final int MATCH_AT_START = 2;
16
17 protected static final boolean debugOutput = true;
18
19
20 public DynamicProgrammingAlgorithm()
21 {
22 }
23
24
25 /**
26 * Matches a melody against the query pattern and returns a distance measure.
27 *
28 * @param P The query pattern
29 * @param T The melody to match against the query pattern.
30 * @param matchingMode How to match against the query pattern (start/anywhere etc.)
31 *
32 * @return A distance measure signifying the distance between the query and match patterns.
33 */
34 public int matchToPattern(Melody P, Melody T, int matchingMode)
35 {
36 // int maxk = options.getIntegerOptionValue("approximation");
37 int maxk = Integer.MAX_VALUE;
38
39 int m = P.getLength();
40 int n = T.getLength();
41
42 // For the comparison to be meaningful the pitch types must be the same
43 if (P.getPitchType() != T.getPitchType())
44 throw new Error("Internal Error: Pitch type of query and match differs.");
45
46 // Check whether the algorithm has some special functionality
47 boolean hasSpecialFunctionality = hasSpecialFunctionality();
48
49 // Allocate distance matrix
50 int[][] D = new int[n + 1][m + 1];
51 D[0][0] = 0;
52
53 // Initialise top row of matrix
54 if (matchingMode == MATCH_AT_START) {
55 // Match at start: have to pay to start further into the pattern
56 for (int i = 1; i <= n; i++)
57 D[i][0] = D[i-1][0] + weightFunction(null, T.getEvent(i-1));
58 }
59 if (matchingMode == MATCH_ANYWHERE) {
60 // Match anywhere: free to start at any point in the pattern
61 for (int i = 1; i <= n; i++)
62 D[i][0] = 0;
63 }
64
65 // Initialise left column of matrix
66 for (int j = 1; j <= m; j++)
67 D[0][j] = D[0][j-1] + weightFunction(P.getEvent(j-1), null);
68
69 // Fill the distance matrix
70 for (int j = 1; j <= m; j++) {
71 MelodyEvent queryEvent = P.getEvent(j-1);
72 for (int i = 1; i <= n; i++) {
73 MelodyEvent matchEvent = T.getEvent(i-1);
74
75 // Case 1: The two events are the same or different (replacement)
76 D[i][j] = D[i-1][j-1] + weightFunction(queryEvent, matchEvent);
77
78 // Case 2: The two events are different (deletion)
79 D[i][j] = Math.min(D[i][j], (D[i-1][j] + weightFunction(null, matchEvent)));
80
81 // Case 3: The two events are different (insertion)
82 D[i][j] = Math.min(D[i][j], (D[i][j-1] + weightFunction(queryEvent, null)));
83
84 // If the algorithm has special functionality, give it a chance now
85 if (hasSpecialFunctionality)
86 doAlgorithmSpecifics(D, i, j, P, T, queryEvent, matchEvent);
87 }
88 }
89
90 // Display the distance matrix
91 if (debugOutput)
92 displayMatrix(D, m, P, n, T);
93
94 // Find and return the minimum edit distance
95 int minDist = Integer.MAX_VALUE;
96 for (int i = 0; i <= n; i++) { // Changed from i = 1 to i = 0 for McNab compatibility
97 if (D[i][m] < minDist)
98 minDist = D[i][m];
99 }
100
101 // Return -1 if the minimum distance is greater than the approximation value
102 return ((minDist > maxk) ? -1 : minDist);
103 }
104
105
106 /**
107 * Returns a distance measure signifying the distance between the query and match events.
108 * This must be implemented by concrete subclasses.
109 *
110 * @param queryEvent The event to compare from the query pattern.
111 * @param matchEvent The event to compare from the match pattern.
112 *
113 * @return a distance measure signifying the distance between the query and match events.
114 */
115 protected abstract int weightFunction(MelodyEvent queryEvent, MelodyEvent matchEvent);
116
117
118 /**
119 * Returns whether the algorithm has special functionality or not.
120 * This should be overridden by concrete subclasses that have special functionality.
121 *
122 * @return true if the algorithm has special functionality, false otherwise.
123 */
124 protected boolean hasSpecialFunctionality()
125 {
126 return false;
127 }
128
129
130 /**
131 * Gives an algorithm a chance to perform any special functionality that it has.
132 * This should be overridden by concrete subclasses that have special functionality.
133 *
134 * @param D The distance matrix at the current point in time.
135 * @param i The current x position in the distance matrix.
136 * @param j The current y position in the distance matrix.
137 * @param T The melody that is being matched to the query pattern.
138 * @param queryEvent The melody event at position j-1 in the query pattern.
139 * @param matchEvent The melody event at position i-1 in the match pattern.
140 */
141 protected void doAlgorithmSpecifics(int[][] D, int i, int j, Melody P, Melody T,
142 MelodyEvent queryEvent, MelodyEvent matchEvent)
143 {
144 // Nothing to do
145 }
146
147
148 /**
149 * Displays a completed distance matrix.
150 *
151 * @param matrix The distance matrix to display.
152 * @param P The query pattern (displayed down the left).
153 * @param T The match pattern (displayed along the top).
154 */
155 private void displayMatrix(int[][] matrix, int m, Melody P, int n, Melody T)
156 {
157 int width = 5;
158
159 // Display the top line (match pattern)
160 System.out.print(rightJustifyString("", width));
161 System.out.print(" ");
162 System.out.print(rightJustifyString("", width));
163 for (int i = 1; i <= n; i++) {
164 MelodyEvent matchEvent = T.getEvent(i-1);
165 int matchPitch = matchEvent.getPitch();
166 String pitchString;
167 if (matchPitch == MelodyEvent.REST_EVENT)
168 pitchString = "REST";
169 else if (matchPitch == MelodyEvent.WILD_EVENT)
170 pitchString = "WILD";
171 else
172 pitchString = String.valueOf(matchPitch);
173
174 System.out.print(rightJustifyString(pitchString, width));
175 }
176 System.out.print("\n");
177
178 // Display the second line
179 System.out.print(rightJustifyString("", width));
180 System.out.print(" -");
181 for (int i = 0; i < ((n+1) * width); i++)
182 System.out.print("-");
183 System.out.print("\n");
184
185 // Display the remaining lines
186 for (int j = 0; j <= m; j++) {
187 if (j == 0)
188 System.out.print(rightJustifyString("", width));
189 else {
190 MelodyEvent queryEvent = P.getEvent(j-1);
191 int queryPitch = queryEvent.getPitch();
192 String pitchString;
193 if (queryPitch == MelodyEvent.REST_EVENT)
194 pitchString = "REST";
195 else if (queryPitch == MelodyEvent.WILD_EVENT)
196 pitchString = "WILD";
197 else
198 pitchString = String.valueOf(queryPitch);
199
200 System.out.print(rightJustifyString(pitchString, width));
201 }
202 System.out.print(" |");
203
204 for (int i = 0; i <= n; i++)
205 System.out.print(rightJustifyString(String.valueOf(matrix[i][j]), width));
206 System.out.print("\n");
207 }
208 }
209
210
211 /**
212 * Right-justifies a string by adding spaces before it.
213 *
214 * @param source The string to right-justify.
215 * @param width The number of characters that the right-justified string will take up.
216 *
217 * @return The right-justified string, or the original string if the width is too small.
218 */
219 private String rightJustifyString(String source, int width)
220 {
221 String result = source;
222
223 // Add width - source.length spaces to the start of the string
224 for (int i = 0; i < (width - source.length()); i++)
225 result = " " + result;
226
227 return result;
228 }
229}
Note: See TracBrowser for help on using the repository browser.