Mercurial > hg > silvet
diff src/AgentFeederPoly.h @ 181:10e7c3ff575e noteagent
Experimental branch toward note-agent stuff (not actually plumbed in yet)
author | Chris Cannam |
---|---|
date | Fri, 23 May 2014 12:40:18 +0100 |
parents | |
children | 9b9cdfccbd14 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/AgentFeederPoly.h Fri May 23 12:40:18 2014 +0100 @@ -0,0 +1,285 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Silvet + + A Vamp plugin for note transcription. + Centre for Digital Music, Queen Mary University of London. + This file Copyright 2012 Chris Cannam. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef AGENT_FEEDER_POLY_H +#define AGENT_FEEDER_POLY_H + +#include "AgentFeeder.h" + +#include <cassert> + +//#define DEBUG_FEEDER 1 + +/** + * Take a series of observations or estimates (one at a time) and feed + * them to a set of agent hypotheses, creating a new candidate agent + * for each observation and also testing the observation against the + * existing set of hypotheses. + * + *!!! -- todo: document poly-ness of it + * + * Call feed() to provide a new observation. Call finish() when all + * observations have been provided. The set of hypotheses returned by + * getAcceptedHypotheses() will not be complete unless finish() has + * been called. + */ +template <typename Hypothesis> +class AgentFeederPoly : public AgentFeeder +{ +public: + typedef std::set<Hypothesis> Hypotheses; + +private: + struct State { + Hypotheses provisional; + Hypotheses satisfied; + Hypotheses completed; + }; + State m_state; + +public: + AgentFeederPoly() { } + + virtual void feed(AgentHypothesis::Observation o) { +#ifdef DEBUG_FEEDER + std::cerr << "feed: have observation [value = " << o.value << ", time = " << o.time << "]" << std::endl; +#endif + + update(m_state, o); + } + + virtual void finish() { +#ifdef DEBUG_FEEDER + std::cerr << "finish: satisfied count == " << m_state.satisfied.size() << std::endl; +#endif + for (typename Hypotheses::const_iterator i = m_state.satisfied.begin(); + i != m_state.satisfied.end(); ++i) { + m_state.completed.insert(*i); + } + } + + Hypotheses getAcceptedHypotheses() const { + return m_state.completed; + } + +private: + void update(State &s, AgentHypothesis::Observation o) { + + /* + An observation can "belong" to any number of provisional + hypotheses, but only to one satisfied hypothesis. + + A new observation is first offered to the hypotheses that + have already been satisfied. If one of these accepts it, it + gets to keep it and no other hypothesis can have it. + + Any observation not accepted by a hypothesis in satisfied + state is then offered to the provisional hypotheses; any + number of these may accept it. Also, every observation that + belongs to no satisfied hypothesis is used as the first + observation in its own new hypothesis (regardless of how + many other provisional hypotheses have accepted it). + + When a hypothesis subsequently becomes satisfied, all other + provisional hypotheses containing any of its observations + must be discarded. + */ + + // We only ever add to the completed hypotheses, never remove + // anything from them. But we may remove from provisional (if + // rejected or transferred to satisfied) and satisfied (when + // completed). + + Hypotheses toCompleted; + Hypotheses toSatisfied; + Hypotheses toProvisional; + + bool swallowed = false; + + for (typename Hypotheses::iterator i = s.satisfied.begin(); + i != s.satisfied.end(); ++i) { + + Hypothesis h = *i; + + assert(h.getState() == Hypothesis::Satisfied); + + if (swallowed) { + + // An observation that has already been accepted by a + // hypothesis cannot be offered to any other, because + // it can only belong to one satisfied hypothesis. Any + // subsequent satisfied hypotheses are retained + // unchanged in our updated state. We can't test them + // for expiry, because the state is only updated when + // accept() is called. + //!!! That looks like a limitation in the Hypothesis API + + } else { // !swallowed + + if (h.accept(o)) { +#ifdef DEBUG_FEEDER + std::cerr << "accepted by satisfied hypothesis " << &(*i) << ", state is " << h.getState() << std::endl; +#endif + swallowed = true; + } else if (h.getState() == Hypothesis::Expired) { + toCompleted.insert(h); + } + } + } + + if (swallowed) { + +#ifdef DEBUG_FEEDER + std::cerr << "was swallowed by satisfied hypothesis" << std::endl; +#endif + // no provisional hypotheses have become satisfied, no new + // ones have been introduced + + } else { + +#ifdef DEBUG_FEEDER + std::cerr << "remained unswallowed by " << newState.satisfied.size() << " satisfied hypotheses" << std::endl; +#endif + + // not swallowed by any satisfied hypothesis + + Hypothesis promoted; + + for (typename Hypotheses::iterator i = s.provisional.begin(); + i != s.provisional.end(); ++i) { + + Hypothesis h = *i; + + assert(h.getState() == Hypothesis::Provisional); + + // can only have one satisfied hypothesis for each + // observation, so try this only if promoted has not been + // set to something else yet + if (promoted == Hypothesis() && + h.accept(o) && + h.getState() == Hypothesis::Satisfied) { + toSatisfied.insert(h); +#ifdef DEBUG_FEEDER + std::cerr << "promoting a hypothesis to satisfied, have " << newState.satisfied.size() << " satisfied now" << std::endl; +#endif + promoted = h; + } else if (h.getState() != Hypothesis::Rejected) { + // leave as provisional + } + } + + if (promoted == Hypothesis()) { + + // No provisional hypothesis has become satisfied + + Hypothesis h; + h.accept(o); + + if (h.getState() == Hypothesis::Provisional) { + toProvisional.insert(h); + } else if (h.getState() == Hypothesis::Satisfied) { + toSatisfied.insert(h); + } + +#ifdef DEBUG_FEEDER + std::cerr << "update: new hypothesis of state " << h.getState() << ", provisional count -> " << newState.provisional.size() << std::endl; +#endif + + } + } + + for (typename Hypotheses::const_iterator i = toCompleted.begin(); + i != toCompleted.end(); ++i) { + s.satisfied.erase(*i); + s.completed.insert(*i); + } + for (typename Hypotheses::const_iterator i = toSatisfied.begin(); + i != toSatisfied.end(); ++i) { + s.provisional.erase(*i); + s.satisfied.insert(*i); + } + for (typename Hypotheses::const_iterator i = toProvisional.begin(); + i != toProvisional.end(); ++i) { + s.provisional.insert(*i); + } + + reap(s); + } + + void reap(State &s) { + + // "When a hypothesis subsequently becomes satisfied, all + // other provisional hypotheses containing any of its + // observations must be discarded." + + if (s.provisional.empty()) return; + + int reaped = 0; + + Hypotheses toRemove = Hypotheses();; + + for (typename Hypotheses::const_iterator hi = s.provisional.begin(); + hi != s.provisional.end(); ++hi) { + + const AgentHypothesis::Observations obs = + hi->getAcceptedObservations(); + + bool keep = true; + + for (AgentHypothesis::Observations::const_iterator oi = obs.begin(); + oi != obs.end(); ++oi) { + + for (typename Hypotheses::const_iterator si = s.satisfied.end(); + si != s.satisfied.begin(); ) { + + --si; + + const AgentHypothesis::Observations sobs = + si->getAcceptedObservations(); + + if (sobs.find(*oi) != sobs.end()) { + keep = false; + break; + } + } + + if (!keep) { + break; + } + } + + if (!keep) { + toRemove.insert(*hi); + ++reaped; + } + } + + for (typename Hypotheses::const_iterator i = toRemove.begin(); + i != toRemove.end(); ++i) { + s.provisional.erase(i); + } + +#ifdef DEBUG_FEEDER + std::cerr << "reap: have " + << s.satisfied.size() << " satisfied, " + << s.provisional.size() << " provisional, " + << s.completed.size() << " completed, reaped " + << reaped << std::endl; +#endif + } +}; + +#endif