Improved WSPR band hopping band selection

When  band hopping,  coordinated  slots that  are  unavailable may  be
substituted  by  non-coordinated  bands or  non-scheduled  coordinated
bands.

Rather than  randomly choosing a substitute  band, random permutations
of available receive-only and transmit allowed bands are generated and
consumed  one at  a time  when a  substitute band  is required.   This
ensures that  all available  bands get  an airing  on a  regular basis
while selections remain randomized as much as possible within the user
defined constraints of  coordinated bands, transmit ratio  and Rx only
bands.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5616 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
Bill Somerville 2015-06-15 12:04:09 +00:00
parent 9375c73b1c
commit cecb01b446
3 changed files with 77 additions and 42 deletions

View File

@ -4,6 +4,7 @@
#include <QSettings>
#include <QBitArray>
#include <QList>
#include <QSet>
#include <QtWidgets>
#include "SettingsGroup.hpp"
@ -221,6 +222,8 @@ public:
Configuration const * configuration_;
int tx_percent_;
BandList WSPR_bands_;
BandList rx_permutation_;
BandList tx_permutation_;
QWidget * parent_widget_;
// 5 x 10 bit flags representing each hopping band in each period
@ -324,7 +327,7 @@ auto WSPRBandHopping::next_hop () -> Hop
int frequencies_index {-1};
auto const& frequencies = m_->configuration_->frequencies ();
auto const& bands = m_->configuration_->bands ();
auto const& band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
auto band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
// coordinated bands
&& m_->WSPR_bands_.contains (band_name))
@ -334,56 +337,86 @@ auto WSPRBandHopping::next_hop () -> Hop
frequencies_index = frequencies->best_working_frequency (band_name);
}
// if we do not have a configured working frequency we next check
// for a random selection from the other enabled bands in the
// bands matrix
// if we do not have a configured working frequency on the selected
// coordinated hopping band we next pick from a random permutation
// of the other enabled bands in the hopping bands matrix
if (frequencies_index < 0)
{
Dialog::BandList target_bands {m_->WSPR_bands_};
// // remove all coordinated bands here since they are
// // scheduled above and including them in the random choice will
// // give them a biased weighting
// for (auto const& band : coordinated_bands)
// {
// target_bands.removeOne (band);
// }
// build sets of available rx and tx bands
auto target_rx_bands = m_->WSPR_bands_.toSet ();
auto target_tx_bands = target_rx_bands;
for (auto i = 0; i < m_->bands_[period_index].size (); ++i)
{
auto const& band = bands->data (bands->index (i, 0)).toString ();
// remove bands that are not enabled for hopping in this phase
if (!m_->bands_[period_index].testBit (i)
// remove Rx only bands if we are wanting to transmit
|| (tx_next && m_->bands_[5].testBit (i)))
if (!m_->bands_[period_index].testBit (i))
{
target_bands.removeOne (bands->data (bands->index (i, 0)).toString ());
target_rx_bands.remove (band);
target_tx_bands.remove (band);
}
// remove rx only bands from transmit list and vice versa
if (m_->bands_[5].testBit (i))
{
target_tx_bands.remove (band);
}
else
{
target_rx_bands.remove (band);
}
}
auto num_bands = target_bands.size ();
if (num_bands) // we have some extra bands available
// if we have some bands to permute
if (target_rx_bands.size () + target_tx_bands.size ())
{
int target_index = static_cast<int> (qrand () % num_bands); // random choice
// here we have a random choice that is enabled in the
// hopping matrix
frequencies_index = frequencies->best_working_frequency (target_bands[target_index]);
if (frequencies_index >= 0)
if (!(m_->rx_permutation_.size () + m_->tx_permutation_.size ()) // all used up
// or rx list contains a band no longer scheduled
|| !target_rx_bands.contains (m_->rx_permutation_.toSet ())
// or tx list contains a band no longer scheduled for tx
|| !target_tx_bands.contains (m_->tx_permutation_.toSet ()))
{
// build new random permutations
m_->rx_permutation_ = target_rx_bands.toList ();
std::random_shuffle (std::begin (m_->rx_permutation_), std::end (m_->rx_permutation_));
m_->tx_permutation_ = target_tx_bands.toList ();
std::random_shuffle (std::begin (m_->tx_permutation_), std::end (m_->tx_permutation_));
// qDebug () << "New random Rx permutation:" << m_->rx_permutation_
// << "random Tx permutation:" << m_->tx_permutation_;
}
if ((tx_next && m_->tx_permutation_.size ()) || !m_->rx_permutation_.size ())
{
Q_ASSERT (m_->tx_permutation_.size ());
// use one from the current random tx permutation
band_name = m_->tx_permutation_.takeFirst ();
}
else
{
Q_ASSERT (m_->rx_permutation_.size ());
// use one from the current random rx permutation
band_name = m_->rx_permutation_.takeFirst ();
}
// find the first WSPR working frequency for the chosen band
frequencies_index = frequencies->best_working_frequency (band_name);
if (frequencies_index >= 0) // should be a redundant check,
// but to be safe
{
// we can use the random choice
qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
band_index = bands->find (target_bands[target_index]);
if (band_index < 0)
// qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
band_index = bands->find (band_name);
if (band_index < 0) // this shouldn't happen
{
// this shouldn't happen
Q_ASSERT (band_index >= 0);
frequencies_index = -1;
}
}
}
}
}
else
{
band_index += 3;
qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
// qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
// remove from random permutations to stop the coordinated bands
// getting too high a weighting - not perfect but surely helps
m_->rx_permutation_.removeOne (band_name);
m_->tx_permutation_.removeOne (band_name);
}
return {
@ -391,13 +424,13 @@ auto WSPRBandHopping::next_hop () -> Hop
, frequencies_index
, frequencies_index >= 0 // new band
&& !tx_next // not going to Tx anyway
&& m_->bands_[4].testBit (band_index) // tune up required
, frequencies_index >= 0 // new band
&& !tx_next // not going to Tx anyway
&& m_->bands_[4].testBit (band_index) // tune up required
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
, frequencies_index >= 0 // new band
&& tx_next // Tx scheduled
, frequencies_index >= 0 // new band
&& tx_next // Tx scheduled
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
};
}

View File

@ -22,8 +22,9 @@ class QWidget;
//
// http://physics.princeton.edu/pulsar/K1JT/doc/wspr/wspr-main.html
//
// Along with selecting bands a flag indicating that a short tune up
// signal is required for specified bands.
// Along with selecting bands a flag indicating that a short tune up
// signal is required for specified bands before they are used for
// receive.
//
// Provides a Qt property that holds the Tx percentage which is used
// to generate a semi-randomized schedule of period to transmit. This
@ -75,6 +76,7 @@ public:
bool next_is_tx ();
private:
// implementation hidden from public interface
class impl;
pimpl<impl> m_;
};

View File

@ -4328,10 +4328,10 @@ void MainWindow::WSPR_scheduling ()
bool transmit {false};
if (ui->band_hopping_group_box->isChecked ()) {
auto hop_data = m_WSPR_band_hopping.next_hop ();
qDebug () << "hop data: period:" << hop_data.period_name_
<< "frequencies index:" << hop_data.frequencies_index_
<< "tune:" << hop_data.tune_required_
<< "tx:" << hop_data.tx_next_;
// qDebug () << "hop data: period:" << hop_data.period_name_
// << "frequencies index:" << hop_data.frequencies_index_
// << "tune:" << hop_data.tune_required_
// << "tx:" << hop_data.tx_next_;
transmit = hop_data.tx_next_;