{
Q_UNUSED(option);
Q_UNUSED(widget);
- painter->setPen(m_color);
- painter->drawLine(m_startNode->pos(), m_endNode->pos());
+
+ static const double Pi = 3.14159265358979323846264338327950288419717;
+ static double TwoPi = 2.0 * Pi;
+
+ QPointF sourcePoint = m_startNode->pos();
+ QPointF destPoint = m_endNode->pos();
+
+ {
+ QLineF line(sourcePoint, destPoint);
+ qreal length = line.length();
+
+ if (length <= Node::size)
+ return;
+
+ prepareGeometryChange();
+
+ QPointF edgeOffset((line.dx() * Node::size / 2) / length, (line.dy() * Node::size / 2) / length);
+ sourcePoint = line.p1() + edgeOffset;
+ destPoint = line.p2() - edgeOffset;
+ }
+ QLineF line(sourcePoint, destPoint);
+
+ // Draw the line itself
+ painter->setPen(QPen(m_color, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
+ painter->drawLine(line);
+
+ // Draw the arrows
+ double angle = ::acos(line.dx() / line.length());
+ if (line.dy() >= 0)
+ angle = TwoPi - angle;
+ QPointF destArrowP1 = destPoint + QPointF(sin(angle - Pi / 3) * arrowSize,
+ cos(angle - Pi / 3) * arrowSize);
+ QPointF destArrowP2 = destPoint + QPointF(sin(angle - Pi + Pi / 3) * arrowSize,
+ cos(angle - Pi + Pi / 3) * arrowSize);
+
+ painter->setBrush(m_color);
+ painter->drawPolygon(QPolygonF() << line.p1());
+ painter->drawPolygon(QPolygonF() << line.p2() << destArrowP1 << destArrowP2);
+
// painter->drawText(boundingRect(), Qt::AlignCenter, QString::number(m_weight));
}
QRectF Edge::boundingRect() const
{
- QPointF tl(qMin(m_startNode->x(), m_endNode->x()), qMin(m_startNode->y(), m_endNode->y()));
- QPointF br(qMax(m_startNode->x(), m_endNode->x()), qMax(m_startNode->y(), m_endNode->y()));
- return QRectF(tl, br);
+ qreal penWidth = 1;
+ qreal extra = (penWidth + arrowSize) / 2.0;
+
+ return QRectF(m_startNode->pos(), QSizeF(m_endNode->pos().x() - m_startNode->pos().x(),
+ m_endNode->pos().y() - m_startNode->pos().y()))
+ .normalized()
+ .adjusted(-extra, -extra, extra, extra);
}
QPainterPath Edge::shape() const
QColor m_color;
Node *m_startNode;
Node *m_endNode;
+
+ static const int arrowSize = 10;
};
QDataStream &operator<<(QDataStream &s, const Edge &edge);
Graph::Graph(QObject *parent) :
QGraphicsScene(parent), m_nodeModel(new NodeModel(this, this)),
- m_edgeModel(new EdgeModel(this, this)), m_mode(EditMode), lastNode(1), startNode(0)
+ m_edgeModel(new EdgeModel(this, this)), m_mode(EditMode), m_addBothEdges(false), lastNode(1), startNode(0)
{
}
m_nodeList.append(node);
addItem(node);
m_nodeModel->endInsertRows();
+ emit graphChanged();
return node;
}
m_edgeList.append(edge);
addItem(edge);
m_edgeModel->endInsertRows();
+ emit graphChanged();
return edge;
}
m_nodeList.removeAt(i);
m_nodeModel->endRemoveRows();
node->deleteLater();
+ emit graphChanged();
}
void Graph::removeEdge(Edge *edge)
m_edgeList.removeAt(i);
m_edgeModel->endRemoveRows();
edge->deleteLater();
+ emit graphChanged();
}
void Graph::clear()
lastNode = 1;
startNode = 0;
+ emit graphChanged();
}
void Graph::save(QIODevice *dev) const
m_nodeModel->reset();
m_edgeModel->reset();
- return false;
+ return true;
+}
+
+void Graph::update(const QRectF &rect)
+{
+ nodeColorChanged();
+ QGraphicsScene::update(rect);
}
void Graph::mousePressEvent(QGraphicsSceneMouseEvent *event)
if (startNode != node)
{
addEdge(startNode, node);
- addEdge(node, startNode);
+ if (m_addBothEdges)
+ addEdge(node, startNode);
}
startNode = 0;
}
event->ignore();
}
+void Graph::nodeColorChanged() const
+{
+ m_nodeModel->dataChanged(m_nodeModel->index(0, 1), m_nodeModel->index(m_nodeList.count() - 1, 1));
+ labelChanged();
+}
+
void Graph::labelChanged() const
{
m_edgeModel->dataChanged(m_edgeModel->index(0, 0), m_edgeModel->index(m_edgeList.count() - 1, 1));
friend EdgeModel;
Q_OBJECT
Q_PROPERTY(Mode mode READ mode WRITE setMode)
+ Q_PROPERTY(bool addBothEdges READ addBothEdges WRITE setAddBothEdges)
public:
enum Mode {
return m_mode;
}
+ bool addBothEdges() const
+ {
+ return m_addBothEdges;
+ }
+
signals:
+ void graphChanged();
public slots:
void complete();
void setMode(Mode mode)
{
m_mode = mode;
+ startNode = 0;
}
+ void setAddBothEdges(bool arg)
+ {
+ m_addBothEdges = arg;
+ }
+
+
void save(QIODevice *dev) const;
bool load(QIODevice *dev);
+ void update(const QRectF &rect = QRectF());
+
+
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
private:
+ void nodeColorChanged() const;
void labelChanged() const;
NodeList m_nodeList;
// For adding edges
Node *startNode;
+ bool m_addBothEdges;
};
#else
class Graph;
modes->addAction(ui->actionEditMode);
modes->addAction(ui->actionAddNodeMode);
modes->addAction(ui->actionAddEdgeMode);
+ modes->addAction(ui->actionAddBothEdgeMode);
modes->addAction(ui->actionRemoveMode);
ui->actionEditMode->setChecked(true);
ui->modeToolBar->addActions(modes->actions());
pmc = new PMC(graph, this);
connect(modes, SIGNAL(triggered(QAction*)), this, SLOT(modeChanged(QAction*)));
+ connect(graph, SIGNAL(graphChanged()), this, SLOT(graphChanged()));
+ connect(pmc, SIGNAL(log(QString)), this, SLOT(pmcLog(QString)));
+ graph->setSceneRect(QRectF(-500, -500, 1000, 1000));
+/*
Node *n1, *n2;
Edge *e;
n1 = graph->addNode();
e = graph->addEdge(n1, n2);
e->setColor(QColor(0,0,255));
-
+*/
nodeProxy = new QSortFilterProxyModel(this);
edgeProxy = new QSortFilterProxyModel(this);
if (action == ui->actionAddNodeMode)
graph->setMode(Graph::AddNodeMode);
else if (action == ui->actionAddEdgeMode)
+ {
+ graph->setMode(Graph::AddEdgeMode);
+ graph->setAddBothEdges(false);
+ }
+ else if (action == ui->actionAddBothEdgeMode)
+ {
graph->setMode(Graph::AddEdgeMode);
+ graph->setAddBothEdges(true);
+ }
else if (action == ui->actionRemoveMode)
graph->setMode(Graph::RemoveNode);
else
graph->setMode(Graph::EditMode);
}
+void MainWindow::graphChanged()
+{
+ if (pmc->finished())
+ ui->pmcLog->clear();
+ pmc->reset();
+ updateLabels();
+}
+
+void MainWindow::pmcLog(const QString &log)
+{
+ ui->pmcLog->append(log);
+// qDebug() << log;
+}
+
void MainWindow::on_actionNew_triggered()
{
graph->clear();
graph->clear();
}
+void MainWindow::on_m_valueChanged(int m)
+{
+ pmc->setM(m);
+}
+void MainWindow::on_pmcRun_clicked()
+{
+ pmc->setM(ui->m->value());
+ pmc->run();
+ ui->statusBar->showMessage(tr("PMC run complete"));
+ updateLabels();
+ graph->update();
+}
void MainWindow::on_pmcStep_clicked()
{
+ pmc->setM(ui->m->value());
if (pmc->step())
ui->statusBar->showMessage(tr("Last Step complete"));
else
ui->statusBar->showMessage(tr("Step complete"));
+ updateLabels();
+ graph->update();
+}
+
+
+void MainWindow::updateLabels()
+{
+ ui->mRange->setText(QString("0..%1").arg(pmc->maxM()));
+ ui->pmcFailed->setText(pmc->failed() ? "<b><font color=\"red\">Yes" : "<b><font color=\"green\">No");
+ ui->pmcFinished->setText(pmc->finished() ? "<b><font color=\"green\">Yes" : "<b>No");
}
private slots:
void modeChanged(QAction *action);
+ void graphChanged();
+ void pmcLog(const QString &log);
void on_actionNew_triggered();
void on_actionOpen_triggered();
void on_actionGenerateGraph_triggered();
void on_actionClearAll_triggered();
+ void on_m_valueChanged(int m);
+ void on_pmcRun_clicked();
void on_pmcStep_clicked();
private:
+ void updateLabels();
+
Ui::MainWindow *ui;
QSortFilterProxyModel *nodeProxy;
<widget class="QDockWidget" name="dockWidget">
<property name="minimumSize">
<size>
- <width>80</width>
+ <width>365</width>
<height>195</height>
</size>
</property>
<number>8</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_3">
- <widget class="QPushButton" name="pmcStep">
- <property name="geometry">
- <rect>
- <x>70</x>
- <y>0</y>
- <width>75</width>
- <height>23</height>
- </rect>
- </property>
- <property name="text">
- <string>Step</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="pmcRun">
+ <property name="text">
+ <string>Run</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pmcStep">
+ <property name="text">
+ <string>Step</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Input</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>m range:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="mRange">
+ <property name="text">
+ <string>0..1</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>m:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="m">
+ <property name="value">
+ <number>2</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Log</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QTextEdit" name="pmcLog">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Status:</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Finished:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="pmcFinished">
+ <property name="text">
+ <string>No</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Failed:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="pmcFailed">
+ <property name="text">
+ <string>No</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
</widget>
</widget>
<action name="actionEditMode">
<string>Ctrl+Shift+S</string>
</property>
</action>
+ <action name="actionAddBothEdgeMode">
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Add Both Edges Mode</string>
+ </property>
+ <property name="toolTip">
+ <string>Add Both Edges Mode</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
Q_UNUSED(option);
Q_UNUSED(widget);
- QBrush b(m_color);
- painter->setBrush(b);
+ QRadialGradient gradient(-3, -3, 10);
+ gradient.setColorAt(0, Qt::white);
+ gradient.setColorAt(1, m_color);
+ painter->setBrush(gradient);
painter->drawEllipse(QRectF(-size/2, -size/2, size, size));
painter->drawText(QPointF(size/2, size), m_label);
}
m_color = color;
}
+public:
+ static const qreal size;
+
private:
QString m_label;
QColor m_color;
EdgeList m_outgoingEdges;
EdgeList m_incomingEdges;
- static const qreal size;
};
QDataStream &operator<<(QDataStream &s, const Node &node);
Qt::ItemFlags NodeModel::flags(const QModelIndex &index) const
{
- return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
+ if (index.column() < 4)
+ return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
+ return QAbstractTableModel::flags(index);
}
QVariant NodeModel::headerData(int section, Qt::Orientation orientation, int role) const
return "X";
case 3:
return "Y";
+ case 4:
+ return "In Degree";
+ case 5:
+ return "Out Degree";
default:
break;
}
int NodeModel::columnCount(const QModelIndex &) const
{
- return 4;
+ return 6;
}
QVariant NodeModel::data(const QModelIndex &index, int role) const
return n->x();
case 3:
return n->y();
+ case 4:
+ return n->incomingEdges().count();
+ case 5:
+ return n->outgoingEdges().count();
default:
break;
}
#include "node.h"
#include "edge.h"
+#include "combination.h"
+
+#include <QApplication>
+#include <QDebug>
+
PMC::PMC(Graph *g, QObject *parent) :
- QObject(parent), m_graph(g)
+ QObject(parent), m_graph(g), m_m(0)
+{
+}
+
+bool cmp(Node *a, Node *b)
{
+ return a->incomingEdges().count() > b->incomingEdges().count();
}
+void PMC::run()
+{
+ while(!step())
+ QApplication::processEvents();
+}
+
+
bool PMC::step()
{
- Node *next = findHighestDegree();
+ QVector<Node *> nodes = m_graph->nodes().toVector();
+ NodeList nodesByIncomingCount = m_graph->nodes();
- if (!next)
- return true;
+ qSort(nodesByIncomingCount.begin(), nodesByIncomingCount.end(), cmp);
- Edge *e = next->incomingEdges().first();
-// Node *otherNode = e->startNode();
- m_graph->removeEdge(e);
+ // Check if possible
+ bool can = true;
+ bool allAtM = true;
+ for (int i = 0; i < nodesByIncomingCount.count(); ++i)
+ {
+ Node *n = nodesByIncomingCount[i];
+ if (n->incomingEdges().count() != m())
+ allAtM = false;
+ if (n->incomingEdges().count() == m())
+ {
+ n->setColor(Qt::green);
+ continue;
+ }
+ if (n->incomingEdges().count() > m())
+ continue;
+ n->setColor(Qt::red);
+ can = false;
+ }
+ if (!can || allAtM)
+ {
+ if (!can)
+ emit log(tr("Node with less than m incoming edges found. END"));
+ if (allAtM)
+ emit log(tr("Done"));
+ m_failed = !allAtM;
+ m_finished = true;
+ return true;
+ }
- if (next->incomingEdges().count() == m())
- next->setColor(Qt::green);
- else
- next->setColor(Qt::yellow);
- next->update();
- return false;
-}
+ qSort(nodes.begin(), nodes.end());
+ QVector<Node *> combination(nodes.count() - 2 * m() + m() - 1);
-Node *PMC::findHighestDegree() const
-{
- Node *ret = 0;
- foreach(Node *n, m_graph->nodes())
+ for (int i = 0; i < nodesByIncomingCount.count(); ++i)
{
- if (n->incomingEdges().count() <= m())
+ Node *n = nodesByIncomingCount[i];
+
+ if (n->incomingEdges().count() < m())
+ {
+ n->setColor(Qt::red);
+ continue;
+ }
+ n->setColor(Qt::yellow);
+
+ for (int j = 0; j < n->incomingEdges().count(); ++j)
{
- if (n->incomingEdges().count() < m())
+ Edge *e = n->incomingEdges()[j];
+
+ Node *otherNode = e->startNode();
+
+ bool removable = true;
+ // HAKIMI
+ for (int p = 0; p < m() - 1; ++p)
{
- n->setColor(Qt::red);
- n->update();
+ int Ep = nodes.count() - 2 * m() + p;
+ qCopy(nodes.begin(), nodes.begin() + Ep, combination.begin());
+ do {
+ int sum = 0;
+ for (int k = 0; k < Ep; ++k)
+ sum += combination.at(k)->outgoingEdges().count();
+ if (combination.contains(otherNode))
+ --sum;
+
+ if (sum <= p)
+ {
+ removable = false;
+ break;
+ }
+
+ } while(stdcomb::next_combination(nodes.begin(), nodes.end(), combination.begin(), combination.begin() + Ep));
+ if (!removable)
+ break;
}
- continue;
+
+ if (removable)
+ {
+ emit log(tr("Removing edge %1->%2 from %3").arg(e->startNode()->label(), e->endNode()->label(), n->label()));
+ m_graph->removeEdge(e);
+
+ if (n->incomingEdges().count() == m())
+ {
+ emit log(tr("%1 is now at m incoming edges").arg(n->label()));
+ n->setColor(Qt::green);
+ }
+ return false;
+ }
+ else
+ emit log(tr("Cannot remove edge %1->%2 from %3").arg(e->startNode()->label(), e->endNode()->label(), n->label()));
+
}
- if (ret == 0)
- ret = n;
- if (n->incomingEdges().count() > n->outgoingEdges().count())
- ret = n;
+ emit log(tr("No edges to remove from %1").arg(n->label()));
}
- return ret;
+ emit log(tr("Could not remove any edge and requirements not met. END"));
+ m_failed = true;
+ m_finished = true;
+ return true;
+}
+
+void PMC::reset()
+{
+ m_failed = false;
+ m_finished = false;
}
int PMC::m() const
{
- return (m_graph->nodes().count() - 1) / 2;
+ return m_m;
}
-int PMC::p() const
+
+int PMC::maxM() const
{
- return m_graph->nodes().count() - 2 * m();
+ return (m_graph->nodes().count() - 1) / 2;
}
class PMC : public QObject
{
Q_OBJECT
+ Q_PROPERTY(int m READ m WRITE setM)
+ Q_PROPERTY(bool failed READ failed)
+ Q_PROPERTY(bool finished READ finished)
+
public:
explicit PMC(Graph *g, QObject *parent = 0);
-
+
+ int m() const;
+ int maxM() const;
+
+ bool failed() const
+ {
+ return m_failed;
+ }
+
+ bool finished() const
+ {
+ return m_finished;
+ }
+
signals:
+ void log(const QString &log);
public slots:
+ void run();
bool step();
+ void reset();
-private:
- Node *findHighestDegree() const;
- int m() const;
- int p() const;
+ void setM(int m)
+ {
+ m_m = m;
+ }
+private:
Graph *m_graph;
+ int m_m;
+ bool m_failed;
+ bool m_finished;
};
#endif // PMC_H