The Conley-Morse Graphs Software
procrecur.h
Go to the documentation of this file.
1/////////////////////////////////////////////////////////////////////////////
2///
3/// @file procrecur.h
4///
5/// Post-processing of Morse decompositions by quantifying the recurrence.
6/// For each Morse set, the minimum recurrence time of each element
7/// is computed, unless there is no directed path in the map definition.
8/// Please, note that this is experimental code at a developmental stage.
9///
10/// @author Pawel Pilarczyk
11///
12/////////////////////////////////////////////////////////////////////////////
13
14// Copyright (C) 1997-2022 by Pawel Pilarczyk.
15//
16// This file is part of my research software package. This is free software:
17// you can redistribute it and/or modify it under the terms of the GNU
18// General Public License as published by the Free Software Foundation,
19// either version 3 of the License, or (at your option) any later version.
20//
21// This software is distributed in the hope that it will be useful,
22// but WITHOUT ANY WARRANTY; without even the implied warranty of
23// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24// GNU General Public License for more details.
25//
26// You should have received a copy of the GNU General Public License
27// along with this software; see the file "license.txt". If not,
28// please, see <https://www.gnu.org/licenses/>.
29
30// Started on October 8, 2008. Last revision: April 23, 2022.
31
32
33#ifndef _CMGRAPHS_PROCRECUR_H_
34#define _CMGRAPHS_PROCRECUR_H_
35
36// include some standard C++ header files
37#include <sstream>
38#include <fstream>
39#include <string>
40#include <utility>
41
42// include selected header files from the CHomP library
43#include "chomp/system/config.h"
44#include "chomp/system/textfile.h"
45#include "chomp/system/timeused.h"
46#include "chomp/struct/digraph.h"
47#include "chomp/struct/flatmatr.h"
48#include "chomp/struct/multitab.h"
49
50// include local header files
51#include "config.h"
52#include "typedefs.h"
53#include "conindex.h"
54#include "morsedec.h"
55#include "typedyns.h"
56#include "utils.h"
57
58
59namespace custom {
60
61/// Post-processing of Morse decompositions by recurrence quantification.
62namespace procRecurrence {
63
64
65// --------------------------------------------------
66// ------- post-process a Morse decomposition -------
67// --------------------------------------------------
68
69/// Writes a directed graph in the "dot" format.
70template <class GraphType>
71inline void graph2dot (const GraphType &g, std::ostream &out)
72{
73 int_t nVertices = g. countVertices ();
74 out << "digraph G {";
75
76 // output the vertices
77 for (int_t vertex = 0; vertex < nVertices; ++ vertex)
78 {
79 out << (vertex ? " " : "") << "v" << vertex <<
80 " [label=\"" << vertex << "\"]";
81 }
82
83 // output the edges
84 for (int_t vertex = 0; vertex < nVertices; ++ vertex)
85 {
86 int_t nEdges = g. countEdges (vertex);
87 for (int_t edge = 0; edge < nEdges; ++ edge)
88 {
89 int_t target = g. getEdge (vertex, edge);
90 out << " v" << vertex << "->v" << target;
91 }
92 }
93
94 out << "}\n";
95 return;
96} /* graph2dot */
97
98/// Computes minimum recurrence of vertices in the graph.
99/// Fills in the array using operator [].
100/// The size of the array must be equal to the number of nodes of the graph.
101/// If no recurrence can be determined for some vertices (which may happen
102/// if the graph is not strongly connected) then sets the value to 0.
103template <class ArrayType, class GraphType>
104inline void computeRecurrence (ArrayType &recurrence, const GraphType &g)
105{
106 using chomp::homology::sbug;
107
108 // skip the analysis if the size of the set is too big
109 const int maxVertices = 30000;
110 if (g. countVertices () > maxVertices)
111 {
112 sbug << "The number of cubes exceeds " << maxVertices <<
113 ". Skipping the analysis.\n";
114 return;
115 }
116
117 // copy the input graph to a weighted graph data structure
118 // and make a note of self-edges
119 sbug << "Copying the graph...\n";
120 typedef chomp::homology::diGraph<int_t> WGraphType;
121 WGraphType wg;
122 int_t nVertices = g. countVertices ();
123 std::vector<bool> selfEdges (nVertices, false);
124 for (int_t vertex = 0; vertex < nVertices; ++ vertex)
125 {
126 wg. addVertex ();
127 int_t nEdges = g. countEdges (vertex);
128 for (int_t edge = 0; edge < nEdges; ++ edge)
129 {
130 int_t target = g. getEdge (vertex, edge);
131 wg. addEdge (target, 1);
132 if (vertex == target)
133 selfEdges [vertex] = true;
134 }
135 }
136
137 // prepare the shortest paths matrix:
138 // D[i][j] is the shortest path from vertex i to vertex j
139 sbug << "Preparing a " << nVertices << "x" << nVertices <<
140 " matrix (" << (nVertices * nVertices * sizeof(int_t) /
141 1024 / 1024) << " MB)...\n";
142 std::vector<std::vector<signed char> > D (nVertices,
143 std::vector<signed char> (nVertices));
144
145 sbug << "Running Dijkstra " << nVertices << " times...\n";
146 std::vector<int_t> tempResults (nVertices);
147 for (int_t source = 0; source < nVertices; ++ source)
148 {
149 if (source && !(source % 17))
150 showProgress (source, ' ');
151 //sbug << "Dijkstra " << source << "\n";
152 wg. Dijkstra (source, tempResults);
153 //sbug << "Done 1.\n";
154 for (int_t i = 0; i < nVertices; ++ i)
155 {
156 if (tempResults [i] < -128)
157 D [source] [i] = -128;
158 else if (tempResults [i] > 127)
159 D [source] [i] = 127;
160 else
161 D [source] [i] = tempResults [i];
162 }
163 //sbug << "Done 2.\n";
164 }
165
166 sbug << "Computing minimum loop lengths...\n";
167 for (int_t source = 0; source < nVertices; ++ source)
168 {
169 // if there is an edge from source to itself
170 if (selfEdges [source])
171 {
172 recurrence [source] = 1;
173 continue;
174 }
175
176 // otherwise find a minimal loop through another vertex
177 int_t minLoop (0);
178 for (int_t other = 0; other < nVertices; ++ other)
179 {
180 if (other == source)
181 continue;
182 // note: negative length == no path
183 int_t len1 = D [source] [other];
184 int_t len2 = D [other] [source];
185 if ((len1 <= 0) || (len2 <= 0))
186 continue;
187 if ((minLoop == 0) || (len1 + len2 < minLoop))
188 minLoop = len1 + len2;
189 }
190 recurrence [source] = minLoop;
191 }
192
193 return;
194} /* computeRecurrence */
195
196/// Computes Vitali variation of the values on the cubical set.
197/// The current implementation is restricted to dimension 2.
198template <class CubSetType, class Vector>
199inline double computeVitaliVariation (const CubSetType &morseCubes,
200 const Vector &recurrence, long &counter)
201{
202 // if the set is empty or the dimension is not supported then return 0
203 if (morseCubes.empty() || (morseCubes[0].dim() != 2))
204 return 0;
205
206 // determine the range of coordinates
207 typedef typename CubSetType::value_type cubeT;
208 typedef typename cubeT::CoordType coordT;
209 coordT coord [2];
210 morseCubes[0].coord (coord);
211 coordT xMin(coord[0]), xMax(coord[0]+1), yMin(coord[1]), yMax(coord[1]+1);
212 for (int_t n = 1; n < morseCubes.size(); ++ n)
213 {
214 morseCubes[n].coord (coord);
215 if (xMin > coord[0])
216 xMin = coord[0];
217 if (xMax <= coord[0])
218 xMax = coord[0] + 1;
219 if (yMin > coord[1])
220 yMin = coord[1];
221 if (yMax <= coord[1])
222 yMax = coord[1] + 1;
223 }
224
225 // create an array for the recurrence values (-1 means undefined)
226 typedef typename Vector::value_type recT;
227 std::vector<recT> row (yMax - yMin, -1);
228 std::vector<std::vector<recT> > R (xMax - xMin, row);
229 for (int_t n = 0; n < morseCubes.size(); ++ n)
230 {
231 morseCubes[n].coord (coord);
232 (R[coord[0]-xMin])[coord[1]-yMin] = recurrence [n];
233 }
234
235 // compute the variation of the recurrence in the array
236 double vvar = 0;
237 counter = 0;
238 for (int_t x = 0; x < xMax - xMin - 1; ++ x)
239 {
240 for (int_t y = 0; y < yMax - yMin - 1; ++ y)
241 {
242 if ((R[x])[y] <= 0)
243 continue;
244 if (((R[x+1])[y] > 0) && ((R[x])[y+1] > 0) &&
245 ((R[x+1])[y+1] > 0))
246 {
247 double v = (R[x])[y] + (R[x+1])[y+1] -
248 (R[x+1])[y] - (R[x])[y+1];
249 vvar += (v > 0) ? v : -v;
250 ++ counter;
251 }
252 }
253 }
254
255 return vvar;
256} /* computeVitaliVariation */
257
258/// Processes a single Morse set by applying the cycle decomposition method.
259template <class CubSetType, class GraphType, class CubMapType>
260inline void processMorseSet (const CubSetType &morseCubes,
261 const CubSetType &allCubes, const GraphType &g,
262 const CubMapType &theCubMap, const std::string &baseSetFileName)
263{
264 using namespace chomp::homology;
265 using chomp::homology::timeused;
266
267 // measure working time
268 timeused workingTime;
269 workingTime = 0;
270
271 // compute the map graph restricted to the Morse set
272 GraphType gm;
273 computeRestrictedGraph (gm, g, allCubes, morseCubes);
274
275 // compute the period of the graph
276 int_t graphPeriod = computePeriod (gm);
277 sbug << "Graph period of this Morse set: " << graphPeriod << ".\n";
278
279 // compute the recurrence value for each vertex in the graph
280 std::vector<int_t> recurrence (morseCubes.size (), 0);
281 computeRecurrence (recurrence, gm);
282
283 // write summary information
284 int_t recMin (0), recMax (0);
285 size_t countZero (0);
286 for (std::vector<int_t>::const_iterator it = recurrence.begin();
287 it != recurrence.end(); ++ it)
288 {
289 if (*it == 0)
290 ++ countZero;
291 if ((*it > 0) && ((recMin == 0) || (*it < recMin)))
292 recMin = *it;
293 if (*it > recMax)
294 recMax = *it;
295 }
296 sbug << "Recurrence between " << recMin << " and " << recMax <<
297 " found.\n" << countZero <<
298 " non-recurrent cubes were encountered.\n";
299
300 // compute Vitali variation of the recurrence
301 long counter (0);
302 double vvar = computeVitaliVariation (morseCubes, recurrence, counter);
303 sbug << "Vitali variation of the recurrence computed on " << counter <<
304 " quadruples: " << vvar << ".\n";
305
306 // save all the cubes with their recurrence times
307 std::string recFileName (baseSetFileName + ".rec");
308 sbug << "Saving '" << recFileName << "'...\n";
309 std::ofstream f (recFileName. c_str ());
310 for (size_t i = 0; i < recurrence.size (); ++ i)
311 {
312 f << morseCubes [i] << " " << recurrence [i] << "\n";
313 }
314 f. close ();
315
316 // show working time
317 sbug << "Recurrence processing completed in " << workingTime << ".\n";
318
319 return;
320} /* processMorseSet */
321
322/// Post-processes a Morse decomposition by computing a decomposition
323/// of each Morse set into cycle sets and analyzing this decomposition.
324template <class CubSetType, class MorseDecType, class GraphType,
325 class CubMapType>
326inline void processMorseDec (const MorseDecType &morseDec,
327 const CubSetType &allCubes, const GraphType &g,
328 const CubMapType &theCubMap, const CubMapType &,
329 const std::string &cubesFilePrefix,
330 const std::string &procFilePrefix)
331{
332 using namespace chomp::homology;
333
334 if (procFilePrefix.empty() && cubesFilePrefix.empty())
335 {
336 sbug << "WARNING: Skipping recurrence analysis, "
337 "because its results would not be saved.\n";
338 return;
339 }
340
341 std::string prefix = (procFilePrefix.size() ?
342 procFilePrefix : cubesFilePrefix) + "r";
343
344 // show a message on what you are doing
345 sbug << "\n=================================\n";
346 sbug << "====== RECURRENCE ANALYSIS ======\n";
347 sbug << "=================================\n";
348 sbug << "Analyzing the Morse sets and saving results to '" <<
349 prefix << "*'...\n";
350 sbug << "Note: cubesFilePrefix = '" << cubesFilePrefix <<
351 "', procFilePrefix = '" << procFilePrefix << "'.\n";
352
353 // analyze each Morse set
354 int nSets = morseDec. count ();
355 for (int n = 0; n < nSets; ++ n)
356 {
357 // restrict the analysis to the given Morse set
358 sbug << "\nMorse set no. " << n << " (" <<
359 morseDec [n]. size () << " cubes).\n";
360
361 // prepare a base file name for this Morse set
362 std::ostringstream s;
363 s << prefix << n;
364 const std::string baseSetFileName = s. str ();
365
366 // process the Morse set
367 processMorseSet (morseDec [n], allCubes, g,
368 theCubMap, baseSetFileName);
369 }
370 sbug << "============== END ==============\n\n";
371
372 return;
373} /* processMorseDec */
374
375
376} // namespace procRecurrence
377} // namespace custom
378
379
380#endif // _CMGRAPHS_PROCRECUR_H_
381
Choice of configuration settings.
Conley index computation routines.
Morse decompositions.
void processMorseDec(const MorseDecType &morseDec, const CubSetType &allCubes, const GraphType &g, const CubMapType &theCubMap, const CubMapType &, const std::string &cubesFilePrefix, const std::string &procFilePrefix)
Post-processes a Morse decomposition by computing a decomposition of each Morse set into cycle sets a...
Definition: procrecur.h:326
double computeVitaliVariation(const CubSetType &morseCubes, const Vector &recurrence, long &counter)
Computes Vitali variation of the values on the cubical set.
Definition: procrecur.h:199
void processMorseSet(const CubSetType &morseCubes, const CubSetType &allCubes, const GraphType &g, const CubMapType &theCubMap, const std::string &baseSetFileName)
Processes a single Morse set by applying the cycle decomposition method.
Definition: procrecur.h:260
void graph2dot(const GraphType &g, std::ostream &out)
Writes a directed graph in the "dot" format.
Definition: procrecur.h:71
void computeRecurrence(ArrayType &recurrence, const GraphType &g)
Computes minimum recurrence of vertices in the graph.
Definition: procrecur.h:104
Customizable settings that are supposed to be modified and/or chosen by the user of the software.
Customizable data types for the Conley-Morse graphs computation program.
Data types for the dynamical systems data structures.
Utilites and helper functions.
void showProgress(int n, char c)
Shows a progress indicator to the screen as an integer number followed by an additional character (e....
Definition: utils.h:974
void computeRestrictedGraph(GraphType &restr, const GraphType &g, const SetType &full, const SetType &subset)
Computes the graph corresponding to the restriction of the combinatorial map to the provided subset.
Definition: utils.h:930