Chris@181
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@181
|
2
|
Chris@181
|
3 /*
|
Chris@181
|
4 Silvet
|
Chris@181
|
5
|
Chris@181
|
6 A Vamp plugin for note transcription.
|
Chris@181
|
7 Centre for Digital Music, Queen Mary University of London.
|
Chris@181
|
8 This file Copyright 2012 Chris Cannam.
|
Chris@181
|
9
|
Chris@181
|
10 This program is free software; you can redistribute it and/or
|
Chris@181
|
11 modify it under the terms of the GNU General Public License as
|
Chris@181
|
12 published by the Free Software Foundation; either version 2 of the
|
Chris@181
|
13 License, or (at your option) any later version. See the file
|
Chris@181
|
14 COPYING included with this distribution for more information.
|
Chris@181
|
15 */
|
Chris@181
|
16
|
Chris@181
|
17 #ifndef AGENT_FEEDER_POLY_H
|
Chris@181
|
18 #define AGENT_FEEDER_POLY_H
|
Chris@181
|
19
|
Chris@181
|
20 #include "AgentFeeder.h"
|
Chris@181
|
21
|
Chris@181
|
22 #include <cassert>
|
Chris@184
|
23 #include <stdexcept>
|
Chris@181
|
24
|
Chris@184
|
25 #define DEBUG_FEEDER 1
|
Chris@181
|
26
|
Chris@181
|
27 /**
|
Chris@181
|
28 * Take a series of observations or estimates (one at a time) and feed
|
Chris@181
|
29 * them to a set of agent hypotheses, creating a new candidate agent
|
Chris@181
|
30 * for each observation and also testing the observation against the
|
Chris@181
|
31 * existing set of hypotheses.
|
Chris@181
|
32 *
|
Chris@181
|
33 *!!! -- todo: document poly-ness of it
|
Chris@181
|
34 *
|
Chris@181
|
35 * Call feed() to provide a new observation. Call finish() when all
|
Chris@181
|
36 * observations have been provided. The set of hypotheses returned by
|
Chris@181
|
37 * getAcceptedHypotheses() will not be complete unless finish() has
|
Chris@181
|
38 * been called.
|
Chris@181
|
39 */
|
Chris@181
|
40 template <typename Hypothesis>
|
Chris@181
|
41 class AgentFeederPoly : public AgentFeeder
|
Chris@181
|
42 {
|
Chris@181
|
43 private:
|
Chris@184
|
44 typedef std::vector<Hypothesis> Hypotheses;
|
Chris@184
|
45
|
Chris@181
|
46 struct State {
|
Chris@181
|
47 Hypotheses provisional;
|
Chris@181
|
48 Hypotheses satisfied;
|
Chris@181
|
49 Hypotheses completed;
|
Chris@181
|
50 };
|
Chris@181
|
51 State m_state;
|
Chris@181
|
52
|
Chris@181
|
53 public:
|
Chris@181
|
54 AgentFeederPoly() { }
|
Chris@181
|
55
|
Chris@181
|
56 virtual void feed(AgentHypothesis::Observation o) {
|
Chris@181
|
57 #ifdef DEBUG_FEEDER
|
Chris@184
|
58 std::cerr << "\nfeed: have observation [value = " << o.value << ", time = " << o.time << "]" << std::endl;
|
Chris@181
|
59 #endif
|
Chris@181
|
60
|
Chris@184
|
61 m_state = update(m_state, o);
|
Chris@181
|
62 }
|
Chris@181
|
63
|
Chris@181
|
64 virtual void finish() {
|
Chris@181
|
65 #ifdef DEBUG_FEEDER
|
Chris@181
|
66 std::cerr << "finish: satisfied count == " << m_state.satisfied.size() << std::endl;
|
Chris@181
|
67 #endif
|
Chris@181
|
68 for (typename Hypotheses::const_iterator i = m_state.satisfied.begin();
|
Chris@181
|
69 i != m_state.satisfied.end(); ++i) {
|
Chris@184
|
70 m_state.completed.push_back(*i);
|
Chris@181
|
71 }
|
Chris@181
|
72 }
|
Chris@181
|
73
|
Chris@187
|
74 std::set<Hypothesis> retrieveAcceptedHypotheses() {
|
Chris@184
|
75 std::set<Hypothesis> hs;
|
Chris@184
|
76 for (typename Hypotheses::const_iterator i = m_state.completed.begin();
|
Chris@184
|
77 i != m_state.completed.end(); ++i) {
|
Chris@184
|
78 hs.insert(*i);
|
Chris@184
|
79 }
|
Chris@187
|
80 m_state.completed.clear();
|
Chris@184
|
81 return hs;
|
Chris@181
|
82 }
|
Chris@181
|
83
|
Chris@181
|
84 private:
|
Chris@184
|
85 State update(State s, AgentHypothesis::Observation o) {
|
Chris@181
|
86
|
Chris@181
|
87 /*
|
Chris@181
|
88 An observation can "belong" to any number of provisional
|
Chris@181
|
89 hypotheses, but only to one satisfied hypothesis.
|
Chris@181
|
90
|
Chris@181
|
91 A new observation is first offered to the hypotheses that
|
Chris@181
|
92 have already been satisfied. If one of these accepts it, it
|
Chris@181
|
93 gets to keep it and no other hypothesis can have it.
|
Chris@181
|
94
|
Chris@181
|
95 Any observation not accepted by a hypothesis in satisfied
|
Chris@181
|
96 state is then offered to the provisional hypotheses; any
|
Chris@181
|
97 number of these may accept it. Also, every observation that
|
Chris@181
|
98 belongs to no satisfied hypothesis is used as the first
|
Chris@181
|
99 observation in its own new hypothesis (regardless of how
|
Chris@181
|
100 many other provisional hypotheses have accepted it).
|
Chris@181
|
101
|
Chris@181
|
102 When a hypothesis subsequently becomes satisfied, all other
|
Chris@181
|
103 provisional hypotheses containing any of its observations
|
Chris@181
|
104 must be discarded.
|
Chris@181
|
105 */
|
Chris@181
|
106
|
Chris@184
|
107 State newState;
|
Chris@184
|
108
|
Chris@181
|
109 // We only ever add to the completed hypotheses, never remove
|
Chris@181
|
110 // anything from them. But we may remove from provisional (if
|
Chris@181
|
111 // rejected or transferred to satisfied) and satisfied (when
|
Chris@181
|
112 // completed).
|
Chris@184
|
113
|
Chris@184
|
114 newState.completed = s.completed;
|
Chris@181
|
115
|
Chris@181
|
116 bool swallowed = false;
|
Chris@181
|
117
|
Chris@181
|
118 for (typename Hypotheses::iterator i = s.satisfied.begin();
|
Chris@181
|
119 i != s.satisfied.end(); ++i) {
|
Chris@181
|
120
|
Chris@181
|
121 Hypothesis h = *i;
|
Chris@181
|
122
|
Chris@181
|
123 assert(h.getState() == Hypothesis::Satisfied);
|
Chris@181
|
124
|
Chris@181
|
125 if (swallowed) {
|
Chris@181
|
126
|
Chris@181
|
127 // An observation that has already been accepted by a
|
Chris@181
|
128 // hypothesis cannot be offered to any other, because
|
Chris@181
|
129 // it can only belong to one satisfied hypothesis. Any
|
Chris@181
|
130 // subsequent satisfied hypotheses are retained
|
Chris@181
|
131 // unchanged in our updated state. We can't test them
|
Chris@181
|
132 // for expiry, because the state is only updated when
|
Chris@181
|
133 // accept() is called.
|
Chris@181
|
134 //!!! That looks like a limitation in the Hypothesis API
|
Chris@184
|
135 newState.satisfied.push_back(h);
|
Chris@181
|
136
|
Chris@181
|
137 } else { // !swallowed
|
Chris@181
|
138
|
Chris@181
|
139 if (h.accept(o)) {
|
Chris@181
|
140 #ifdef DEBUG_FEEDER
|
Chris@181
|
141 std::cerr << "accepted by satisfied hypothesis " << &(*i) << ", state is " << h.getState() << std::endl;
|
Chris@181
|
142 #endif
|
Chris@181
|
143 swallowed = true;
|
Chris@184
|
144 newState.satisfied.push_back(h);
|
Chris@181
|
145 } else if (h.getState() == Hypothesis::Expired) {
|
Chris@184
|
146 newState.completed.push_back(h);
|
Chris@184
|
147 } else {
|
Chris@184
|
148 newState.satisfied.push_back(h);
|
Chris@181
|
149 }
|
Chris@181
|
150 }
|
Chris@181
|
151 }
|
Chris@181
|
152
|
Chris@181
|
153 if (swallowed) {
|
Chris@181
|
154
|
Chris@181
|
155 #ifdef DEBUG_FEEDER
|
Chris@181
|
156 std::cerr << "was swallowed by satisfied hypothesis" << std::endl;
|
Chris@181
|
157 #endif
|
Chris@181
|
158 // no provisional hypotheses have become satisfied, no new
|
Chris@181
|
159 // ones have been introduced
|
Chris@184
|
160 newState.provisional = s.provisional;
|
Chris@181
|
161
|
Chris@181
|
162 } else {
|
Chris@181
|
163
|
Chris@181
|
164 #ifdef DEBUG_FEEDER
|
Chris@181
|
165 std::cerr << "remained unswallowed by " << newState.satisfied.size() << " satisfied hypotheses" << std::endl;
|
Chris@181
|
166 #endif
|
Chris@181
|
167
|
Chris@181
|
168 // not swallowed by any satisfied hypothesis
|
Chris@181
|
169
|
Chris@181
|
170 Hypothesis promoted;
|
Chris@181
|
171
|
Chris@181
|
172 for (typename Hypotheses::iterator i = s.provisional.begin();
|
Chris@181
|
173 i != s.provisional.end(); ++i) {
|
Chris@181
|
174
|
Chris@181
|
175 Hypothesis h = *i;
|
Chris@181
|
176
|
Chris@181
|
177 assert(h.getState() == Hypothesis::Provisional);
|
Chris@181
|
178
|
Chris@181
|
179 // can only have one satisfied hypothesis for each
|
Chris@181
|
180 // observation, so try this only if promoted has not been
|
Chris@181
|
181 // set to something else yet
|
Chris@181
|
182 if (promoted == Hypothesis() &&
|
Chris@181
|
183 h.accept(o) &&
|
Chris@181
|
184 h.getState() == Hypothesis::Satisfied) {
|
Chris@184
|
185 newState.satisfied.push_back(h);
|
Chris@181
|
186 #ifdef DEBUG_FEEDER
|
Chris@181
|
187 std::cerr << "promoting a hypothesis to satisfied, have " << newState.satisfied.size() << " satisfied now" << std::endl;
|
Chris@181
|
188 #endif
|
Chris@181
|
189 promoted = h;
|
Chris@181
|
190 } else if (h.getState() != Hypothesis::Rejected) {
|
Chris@184
|
191 newState.provisional.push_back(h);
|
Chris@181
|
192 }
|
Chris@181
|
193 }
|
Chris@181
|
194
|
Chris@181
|
195 if (promoted == Hypothesis()) {
|
Chris@181
|
196
|
Chris@181
|
197 // No provisional hypothesis has become satisfied
|
Chris@181
|
198
|
Chris@181
|
199 Hypothesis h;
|
Chris@181
|
200 h.accept(o);
|
Chris@181
|
201
|
Chris@181
|
202 if (h.getState() == Hypothesis::Provisional) {
|
Chris@184
|
203 newState.provisional.push_back(h);
|
Chris@181
|
204 } else if (h.getState() == Hypothesis::Satisfied) {
|
Chris@184
|
205 newState.satisfied.push_back(h);
|
Chris@181
|
206 }
|
Chris@181
|
207
|
Chris@181
|
208 #ifdef DEBUG_FEEDER
|
Chris@181
|
209 std::cerr << "update: new hypothesis of state " << h.getState() << ", provisional count -> " << newState.provisional.size() << std::endl;
|
Chris@181
|
210 #endif
|
Chris@184
|
211 } else {
|
Chris@181
|
212
|
Chris@184
|
213 #ifdef DEBUG_FEEDER
|
Chris@184
|
214 std::cerr << "a hypothesis became satisfied, reaping its observations" << std::endl;
|
Chris@184
|
215 #endif
|
Chris@184
|
216 newState = reap(newState);
|
Chris@181
|
217 }
|
Chris@181
|
218 }
|
Chris@181
|
219
|
Chris@184
|
220 return newState;
|
Chris@181
|
221 }
|
Chris@181
|
222
|
Chris@184
|
223 State reap(State s) {
|
Chris@181
|
224
|
Chris@181
|
225 // "When a hypothesis subsequently becomes satisfied, all
|
Chris@181
|
226 // other provisional hypotheses containing any of its
|
Chris@181
|
227 // observations must be discarded."
|
Chris@181
|
228
|
Chris@184
|
229 if (s.provisional.empty()) return s;
|
Chris@181
|
230
|
Chris@181
|
231 int reaped = 0;
|
Chris@181
|
232
|
Chris@184
|
233 Hypotheses prior = s.provisional;
|
Chris@184
|
234 s.provisional = Hypotheses();
|
Chris@181
|
235
|
Chris@184
|
236 for (typename Hypotheses::const_iterator hi = prior.begin();
|
Chris@184
|
237 hi != prior.end(); ++hi) {
|
Chris@181
|
238
|
Chris@181
|
239 const AgentHypothesis::Observations obs =
|
Chris@181
|
240 hi->getAcceptedObservations();
|
Chris@181
|
241
|
Chris@181
|
242 bool keep = true;
|
Chris@181
|
243
|
Chris@181
|
244 for (AgentHypothesis::Observations::const_iterator oi = obs.begin();
|
Chris@181
|
245 oi != obs.end(); ++oi) {
|
Chris@181
|
246
|
Chris@181
|
247 for (typename Hypotheses::const_iterator si = s.satisfied.end();
|
Chris@181
|
248 si != s.satisfied.begin(); ) {
|
Chris@181
|
249
|
Chris@181
|
250 --si;
|
Chris@181
|
251
|
Chris@181
|
252 const AgentHypothesis::Observations sobs =
|
Chris@181
|
253 si->getAcceptedObservations();
|
Chris@181
|
254
|
Chris@181
|
255 if (sobs.find(*oi) != sobs.end()) {
|
Chris@181
|
256 keep = false;
|
Chris@181
|
257 break;
|
Chris@181
|
258 }
|
Chris@181
|
259 }
|
Chris@181
|
260
|
Chris@181
|
261 if (!keep) {
|
Chris@181
|
262 break;
|
Chris@181
|
263 }
|
Chris@181
|
264 }
|
Chris@181
|
265
|
Chris@184
|
266 if (keep) {
|
Chris@184
|
267 s.provisional.push_back(*hi);
|
Chris@184
|
268 } else {
|
Chris@181
|
269 ++reaped;
|
Chris@181
|
270 }
|
Chris@181
|
271 }
|
Chris@181
|
272
|
Chris@181
|
273 #ifdef DEBUG_FEEDER
|
Chris@181
|
274 std::cerr << "reap: have "
|
Chris@181
|
275 << s.satisfied.size() << " satisfied, "
|
Chris@181
|
276 << s.provisional.size() << " provisional, "
|
Chris@181
|
277 << s.completed.size() << " completed, reaped "
|
Chris@181
|
278 << reaped << std::endl;
|
Chris@181
|
279 #endif
|
Chris@184
|
280
|
Chris@184
|
281 return s;
|
Chris@181
|
282 }
|
Chris@181
|
283 };
|
Chris@181
|
284
|
Chris@181
|
285 #endif
|