occam-directed-graphs 3.0.36 → 3.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Occam Directed Graphs
2
2
 
3
- Directed graphs comprised of directed acyclic graphs together with cycles for [Occam](https://github.com/djalbat/occam).
3
+ [Occam](https://github.com/djalbat/occam)'s directed graphs.
4
4
 
5
5
  ### Contents
6
6
 
@@ -8,12 +8,13 @@ Directed graphs comprised of directed acyclic graphs together with cycles for [O
8
8
  - [Installation](#installation)
9
9
  - [Usage](#usage)
10
10
  - [Building](#building)
11
+ - [Running the tests](#running-the-tests)
11
12
  - [References](#references)
12
13
  - [Contact](#contact)
13
14
 
14
15
  ## Introduction
15
16
 
16
- The reason for not simply implementing a directed graph trivially is to keep track of cycles and recover them quickly. Given the name of any vertex, the implementation will report whether or not it is part of a cycle. If it is, you can retrieve a cycle of which it is part, but bear in mind that a vertex may be the part of more than one cycle, any one of which can be returned.
17
+ This implementation extends an incremental algorithm for directed graphs, one that returns a topological ordering for the graph. If one or more cyclic edges are added then the extended algorithm handles it gracefully rather than throwing an error. In these cases a cycle can be returned rather than a topological ordering. If all cyclic edges are removed thereafter then the algorithm recovers and a topological ordering can be returned again.
17
18
 
18
19
  ## Installation
19
20
 
@@ -31,53 +32,47 @@ You can also clone the repository with [Git](https://git-scm.com/)...
31
32
 
32
33
  ## Usage
33
34
 
34
- An empty directed graph can be created by calling the `fromNothing()` factory method, after which vertices and edges can be added incrementally:
35
+ An empty directed graph can be created by calling the `fromNothing()` factory method:
35
36
 
36
37
  ```
37
38
  import { DirectedGraph } from "occam-directed-graphs";
38
39
 
39
- const directedGraph = DirectedGraph.fromNothing(),
40
- vertexName = "i",
41
- sourceVertexName = "j",
42
- targetVertexName = "k";
40
+ const directedGraph = DirectedGraph.fromNothing();
41
+ ```
43
42
 
44
- directedGraph.addVertexByVertexName(vertexName);
43
+ After instantiation, vertices and edges can be added incrementally:
45
44
 
46
- directedGraph.addEdgeByVertexNames(sourceVertexName, targetVertexName);
47
45
  ```
46
+ import { Edge } from "occam-directed-graphs";
48
47
 
49
- Note that there is no need to add vertices explicitly, they will be added whenever necessary when edges that reference them are added.
48
+ const vertexName = "i";
50
49
 
51
- Alternatively, a directed graph can be constructed with the `fromVertexLiterals()` factory method as follows:
50
+ directedGraph.addVertexByVertexName(vertexName);
51
+
52
+ const sourceVertexName = "j",
53
+ targetVertexName = "k",
54
+ edge = Edge.fromSourceVertexNameAndTargetVertexName(sourceVertexName, targetVertexName);
52
55
 
56
+ directedGraph.addEdge(edge);
53
57
  ```
54
- import { DirectedGraph } from "occam-directed-graphs";
55
58
 
56
- const graph = Graph.fromVertexLiterals([
59
+ Note that there is no need to add vertices explicitly, they will be added whenever necessary when edges that reference them are added.
57
60
 
58
- ["a", ["b"]],
59
- ["b", ["c"]],
60
- ["d", ["c"]],
61
- ["e", []],
62
- ["f", ["g"]],
63
- ["h", ["g"]]
61
+ You can also remove vertices and edges. Removing a vertex could result in removing edges. When you remove an edge, you can additionally specify that the any stranded vertices that result are also removed:
64
62
 
65
- ]);
66
63
  ```
64
+ import { Edge } from "occam-directed-graphs";
67
65
 
68
- Note that the array of names that is the second element of each literal gives the *ancestors* of the vertex and not its descendants.
66
+ const vertexName = "i";
69
67
 
70
- You can also remove vertices and edges from the graph. Removing a vertex may of course result in removing edges. When you remove an edge, you can additionally specify that the any stranded vertices that result are also removed:
68
+ directedGraph.removeVertexByName(vertexName);
71
69
 
72
- ```
73
- const vertexName = "i",
74
- sourceVertexName = "j",
70
+ const sourceVertexName = "j",
75
71
  targetVertexName = "k",
76
- removeStrandedVertices = true;
77
-
78
- directedGraph.removeVertexByName(vertexName);
72
+ removeStrandedVertices = true,
73
+ edge = Edge.fromSourceVertexNameAndTargetVertexName(sourceVertexName, targetVertexName);
79
74
 
80
- directedGraph.removeEdgeByVertexNames(sourceVertexName, targetVertexName, removeStrandedVertices);
75
+ directedGraph.removeEdge(edge, removeStrandedVertices);
81
76
  ```
82
77
 
83
78
  The default is to leave stranded vertices in place.
@@ -85,33 +80,17 @@ The default is to leave stranded vertices in place.
85
80
  You can detect and recover cycles thus:
86
81
 
87
82
  ```
88
- const vertexName = "i",
89
- cyclePresent = directedGraph.isCyclePresentByVertexName(vertexName);
83
+ const cyclePresent = directedGraph.isCyclePresent();
90
84
 
91
85
  if (cyclePresent) {
92
- const cycle = directedGraph.getFirstCycleByVertexName(vertexName),
93
- cycleVertexNames = cycle.getVertexNames();
86
+ const firstCycle = directedGraph.getFirstCycle(),
87
+ firstCycleVertexNames = firstCycle.getVertexNames();
94
88
 
95
89
  ...
96
90
  }
97
91
  ```
98
- As already pointed out in the introduction, if a vertex is part of more than one cycle, any of of them can be given.
99
-
100
92
  You can get a topologically ordered list of vertices at any time.
101
- Bear in mind that this will gotten from the underlying directed acyclic graph, however, and therefore may not be useful if there are cycles present.
102
- A method is also provided to ascertain whether cycles are generally present and it is left up to you to decide whether the topologically ordered list of vertices is useful in the event that they are:
103
-
104
- ```
105
- const cyclesPresent = directedGraph.areCyclesPresent();
106
-
107
- if (cyclesPresent) {
108
- ///
109
- } else {
110
- const orderedVertexNames = directedGraph.getOrderedVertexNames();
111
-
112
- ...
113
- }
114
- ```
93
+ Bear in mind that this is useless information when cycles are present.
115
94
 
116
95
  ## Building
117
96
 
@@ -120,6 +99,12 @@ Automation is done with [npm scripts](https://docs.npmjs.com/misc/scripts), have
120
99
  npm run build-debug
121
100
  npm run watch-debug
122
101
 
102
+ ## Running the tests
103
+
104
+ Assuming that the packages are installed:
105
+
106
+ npm test
107
+
123
108
  ## References
124
109
 
125
110
  This implementation is based on the Pearce-Kelly algorithm:
package/lib/cycle.js CHANGED
@@ -8,8 +8,15 @@ Object.defineProperty(exports, "default", {
8
8
  return Cycle;
9
9
  }
10
10
  });
11
- var _necessary = require("necessary");
12
11
  var _vertex = require("./utilities/vertex");
12
+ function _array_like_to_array(arr, len) {
13
+ if (len == null || len > arr.length) len = arr.length;
14
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
15
+ return arr2;
16
+ }
17
+ function _array_without_holes(arr) {
18
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
19
+ }
13
20
  function _class_call_check(instance, Constructor) {
14
21
  if (!(instance instanceof Constructor)) {
15
22
  throw new TypeError("Cannot call a class as a function");
@@ -29,7 +36,23 @@ function _create_class(Constructor, protoProps, staticProps) {
29
36
  if (staticProps) _defineProperties(Constructor, staticProps);
30
37
  return Constructor;
31
38
  }
32
- var first = _necessary.arrayUtilities.first;
39
+ function _iterable_to_array(iter) {
40
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
41
+ }
42
+ function _non_iterable_spread() {
43
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
44
+ }
45
+ function _to_consumable_array(arr) {
46
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
47
+ }
48
+ function _unsupported_iterable_to_array(o, minLen) {
49
+ if (!o) return;
50
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
51
+ var n = Object.prototype.toString.call(o).slice(8, -1);
52
+ if (n === "Object" && o.constructor) n = o.constructor.name;
53
+ if (n === "Map" || n === "Set") return Array.from(n);
54
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
55
+ }
33
56
  var Cycle = /*#__PURE__*/ function() {
34
57
  function Cycle(vertexNames) {
35
58
  _class_call_check(this, Cycle);
@@ -44,17 +67,11 @@ var Cycle = /*#__PURE__*/ function() {
44
67
  }
45
68
  ], [
46
69
  {
47
- key: "fromVertexNamePartialCycleAndSuccessorVertices",
48
- value: function fromVertexNamePartialCycleAndSuccessorVertices(vertexName, partialCycle, successorVertices) {
49
- successorVertices = successorVertices.slice(); ///
50
- var successorVerticesLength = successorVertices.length;
51
- if (successorVerticesLength > 0) {
52
- var firstSuccessorVertex = first(successorVertices), firstSuccessorVertexName = firstSuccessorVertex.getName(), cyclicEdgeTargetVertexName = partialCycle.getTargetVertexName();
53
- if (firstSuccessorVertexName === cyclicEdgeTargetVertexName) {
54
- successorVertices.shift();
55
- }
56
- }
57
- var cyclicEdgeSourceVertexName = partialCycle.getCyclicEdgeSourceVertexName(), cyclicEdgeTargetVertexName1 = partialCycle.getCyclicEdgeTargetVertexName(), predecessorVertexNames = partialCycle.getPredecessorVertexNames(), successorVertexNames = (0, _vertex.vertexNamesFromVertices)(successorVertices), vertexNames = vertexName === cyclicEdgeTargetVertexName1 ? [].concat(cyclicEdgeTargetVertexName1).concat(predecessorVertexNames).concat(cyclicEdgeSourceVertexName) : [].concat(predecessorVertexNames).concat(cyclicEdgeSourceVertexName).concat(cyclicEdgeTargetVertexName1).concat(successorVertexNames), cycle = new Cycle(vertexNames);
70
+ key: "fromSourceVertexAndPredecessorVertexes",
71
+ value: function fromSourceVertexAndPredecessorVertexes(sourceVertex, predecessorVertexes) {
72
+ var vertexes = _to_consumable_array(predecessorVertexes).concat([
73
+ sourceVertex
74
+ ]), vertexNames = (0, _vertex.vertexNamesFromVertexes)(vertexes), cycle = new Cycle(vertexNames);
58
75
  return cycle;
59
76
  }
60
77
  }
@@ -62,4 +79,4 @@ var Cycle = /*#__PURE__*/ function() {
62
79
  return Cycle;
63
80
  }();
64
81
 
65
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9jeWNsZS5qcyIsIjw8anN4LWNvbmZpZy1wcmFnbWEuanM+PiJdLCJzb3VyY2VzQ29udGVudCI6WyJcInVzZSBzdHJpY3RcIjtcblxuaW1wb3J0IHsgYXJyYXlVdGlsaXRpZXMgfSBmcm9tIFwibmVjZXNzYXJ5XCI7XG5cbmltcG9ydCB7IHZlcnRleE5hbWVzRnJvbVZlcnRpY2VzIH0gZnJvbSBcIi4vdXRpbGl0aWVzL3ZlcnRleFwiO1xuXG5jb25zdCB7IGZpcnN0IH0gPSBhcnJheVV0aWxpdGllcztcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQ3ljbGUge1xuICBjb25zdHJ1Y3Rvcih2ZXJ0ZXhOYW1lcykge1xuICAgIHRoaXMudmVydGV4TmFtZXMgPSB2ZXJ0ZXhOYW1lcztcbiAgfVxuXG4gIGdldFZlcnRleE5hbWVzKCkge1xuICAgIHJldHVybiB0aGlzLnZlcnRleE5hbWVzO1xuICB9XG5cbiAgc3RhdGljIGZyb21WZXJ0ZXhOYW1lUGFydGlhbEN5Y2xlQW5kU3VjY2Vzc29yVmVydGljZXModmVydGV4TmFtZSwgcGFydGlhbEN5Y2xlLCBzdWNjZXNzb3JWZXJ0aWNlcykge1xuICAgIHN1Y2Nlc3NvclZlcnRpY2VzID0gc3VjY2Vzc29yVmVydGljZXMuc2xpY2UoKTsgIC8vL1xuICAgIFxuICAgIGNvbnN0IHN1Y2Nlc3NvclZlcnRpY2VzTGVuZ3RoID0gc3VjY2Vzc29yVmVydGljZXMubGVuZ3RoO1xuICAgIFxuICAgIGlmIChzdWNjZXNzb3JWZXJ0aWNlc0xlbmd0aCA+IDApIHtcbiAgICAgIGNvbnN0IGZpcnN0U3VjY2Vzc29yVmVydGV4ID0gZmlyc3Qoc3VjY2Vzc29yVmVydGljZXMpLFxuICAgICAgICAgICAgZmlyc3RTdWNjZXNzb3JWZXJ0ZXhOYW1lID0gZmlyc3RTdWNjZXNzb3JWZXJ0ZXguZ2V0TmFtZSgpLFxuICAgICAgICAgICAgY3ljbGljRWRnZVRhcmdldFZlcnRleE5hbWUgPSBwYXJ0aWFsQ3ljbGUuZ2V0VGFyZ2V0VmVydGV4TmFtZSgpO1xuICAgICAgXG4gICAgICBpZiAoZmlyc3RTdWNjZXNzb3JWZXJ0ZXhOYW1lID09PSBjeWNsaWNFZGdlVGFyZ2V0VmVydGV4TmFtZSkge1xuICAgICAgICBzdWNjZXNzb3JWZXJ0aWNlcy5zaGlmdCgpO1xuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGN5Y2xpY0VkZ2VTb3VyY2VWZXJ0ZXhOYW1lID0gcGFydGlhbEN5Y2xlLmdldEN5Y2xpY0VkZ2VTb3VyY2VWZXJ0ZXhOYW1lKCksXG4gICAgICAgICAgY3ljbGljRWRnZVRhcmdldFZlcnRleE5hbWUgPSBwYXJ0aWFsQ3ljbGUuZ2V0Q3ljbGljRWRnZVRhcmdldFZlcnRleE5hbWUoKSxcbiAgICAgICAgICBwcmVkZWNlc3NvclZlcnRleE5hbWVzID0gcGFydGlhbEN5Y2xlLmdldFByZWRlY2Vzc29yVmVydGV4TmFtZXMoKSxcbiAgICAgICAgICBzdWNjZXNzb3JWZXJ0ZXhOYW1lcyA9IHZlcnRleE5hbWVzRnJvbVZlcnRpY2VzKHN1Y2Nlc3NvclZlcnRpY2VzKSxcbiAgICAgICAgICB2ZXJ0ZXhOYW1lcyA9ICh2ZXJ0ZXhOYW1lID09PSBjeWNsaWNFZGdlVGFyZ2V0VmVydGV4TmFtZSkgP1xuICAgICAgICAgICAgICAgICAgICAgICAgICBbXS5jb25jYXQoY3ljbGljRWRnZVRhcmdldFZlcnRleE5hbWUpLmNvbmNhdChwcmVkZWNlc3NvclZlcnRleE5hbWVzKS5jb25jYXQoY3ljbGljRWRnZVNvdXJjZVZlcnRleE5hbWUpIDpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBbXS5jb25jYXQocHJlZGVjZXNzb3JWZXJ0ZXhOYW1lcykuY29uY2F0KGN5Y2xpY0VkZ2VTb3VyY2VWZXJ0ZXhOYW1lKS5jb25jYXQoY3ljbGljRWRnZVRhcmdldFZlcnRleE5hbWUpLmNvbmNhdChzdWNjZXNzb3JWZXJ0ZXhOYW1lcyksXG4gICAgICAgICAgY3ljbGUgPSBuZXcgQ3ljbGUodmVydGV4TmFtZXMpO1xuICAgIFxuICAgIHJldHVybiBjeWNsZTtcbiAgfVxufVxuIiwiUmVhY3QuY3JlYXRlRWxlbWVudCJdLCJuYW1lcyI6WyJDeWNsZSIsImZpcnN0IiwiYXJyYXlVdGlsaXRpZXMiLCJ2ZXJ0ZXhOYW1lcyIsImdldFZlcnRleE5hbWVzIiwiZnJvbVZlcnRleE5hbWVQYXJ0aWFsQ3ljbGVBbmRTdWNjZXNzb3JWZXJ0aWNlcyIsInZlcnRleE5hbWUiLCJwYXJ0aWFsQ3ljbGUiLCJzdWNjZXNzb3JWZXJ0aWNlcyIsInNsaWNlIiwic3VjY2Vzc29yVmVydGljZXNMZW5ndGgiLCJsZW5ndGgiLCJmaXJzdFN1Y2Nlc3NvclZlcnRleCIsImZpcnN0U3VjY2Vzc29yVmVydGV4TmFtZSIsImdldE5hbWUiLCJjeWNsaWNFZGdlVGFyZ2V0VmVydGV4TmFtZSIsImdldFRhcmdldFZlcnRleE5hbWUiLCJzaGlmdCIsImN5Y2xpY0VkZ2VTb3VyY2VWZXJ0ZXhOYW1lIiwiZ2V0Q3ljbGljRWRnZVNvdXJjZVZlcnRleE5hbWUiLCJnZXRDeWNsaWNFZGdlVGFyZ2V0VmVydGV4TmFtZSIsInByZWRlY2Vzc29yVmVydGV4TmFtZXMiLCJnZXRQcmVkZWNlc3NvclZlcnRleE5hbWVzIiwic3VjY2Vzc29yVmVydGV4TmFtZXMiLCJ2ZXJ0ZXhOYW1lc0Zyb21WZXJ0aWNlcyIsImNvbmNhdCIsImN5Y2xlIl0sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7OztlQVFxQkE7Ozt5QkFOVTtzQkFFUzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFeEMsSUFBTSxBQUFFQyxRQUFVQywwQkFBVkQ7QUFFTyxJQUFBLEFBQU1ELHNCQUFOO2FBQU1BLE1BQ1BHLFdBQVc7Z0NBREpIO1FBRWpCLElBQUksQ0FBQ0csY0FBY0E7O2tCQUZGSDs7WUFLbkJJLEtBQUFBO21CQUFBQSxTQUFBQTtnQkFDRSxPQUFPLElBQUksQ0FBQ0Q7WUFDZDs7OztZQUVPRSxLQUFBQTttQkFBUCxTQUFPQSwrQ0FBK0NDLFVBQVUsRUFBRUMsWUFBWSxFQUFFQyxpQkFBaUI7Z0JBQy9GQSxvQkFBb0JBLGtCQUFrQkMsU0FBVSxHQUFHO2dCQUVuRCxJQUFNQywwQkFBMEJGLGtCQUFrQkc7Z0JBRWxELElBQUlELDBCQUEwQixHQUFHO29CQUMvQixJQUFNRSx1QkFBdUJYLE1BQU1PLG9CQUM3QkssMkJBQTJCRCxxQkFBcUJFLFdBQ2hEQyw2QkFBNkJSLGFBQWFTO29CQUVoRCxJQUFJSCw2QkFBNkJFLDRCQUE0Qjt3QkFDM0RQLGtCQUFrQlM7b0JBQ3BCO2dCQUNGO2dCQUVBLElBQU1DLDZCQUE2QlgsYUFBYVksaUNBQzFDSiw4QkFBNkJSLGFBQWFhLGlDQUMxQ0MseUJBQXlCZCxhQUFhZSw2QkFDdENDLHVCQUF1QkMsSUFBQUEsaUNBQXdCaEIsb0JBQy9DTCxjQUFjLEFBQUNHLGVBQWVTLDhCQUNkLEVBQUUsQ0FBQ1UsT0FBT1YsNkJBQTRCVSxPQUFPSix3QkFBd0JJLE9BQU9QLDhCQUMxRSxFQUFFLENBQUNPLE9BQU9KLHdCQUF3QkksT0FBT1AsNEJBQTRCTyxPQUFPViw2QkFBNEJVLE9BQU9GLHVCQUNqSUcsUUFBUSxJQS9CRzFCLE1BK0JPRztnQkFFeEIsT0FBT3VCO1lBQ1Q7OztXQWxDbUIxQiJ9
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9jeWNsZS5qcyIsIjw8anN4LWNvbmZpZy1wcmFnbWEuanM+PiJdLCJzb3VyY2VzQ29udGVudCI6WyJcInVzZSBzdHJpY3RcIjtcblxuaW1wb3J0IHsgdmVydGV4TmFtZXNGcm9tVmVydGV4ZXMgfSBmcm9tIFwiLi91dGlsaXRpZXMvdmVydGV4XCI7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEN5Y2xlIHtcbiAgY29uc3RydWN0b3IodmVydGV4TmFtZXMpIHtcbiAgICB0aGlzLnZlcnRleE5hbWVzID0gdmVydGV4TmFtZXM7XG4gIH1cblxuICBnZXRWZXJ0ZXhOYW1lcygpIHtcbiAgICByZXR1cm4gdGhpcy52ZXJ0ZXhOYW1lcztcbiAgfVxuXG4gIHN0YXRpYyBmcm9tU291cmNlVmVydGV4QW5kUHJlZGVjZXNzb3JWZXJ0ZXhlcyhzb3VyY2VWZXJ0ZXgsIHByZWRlY2Vzc29yVmVydGV4ZXMpIHtcbiAgICBjb25zdCB2ZXJ0ZXhlcyA9IFtcbiAgICAgICAgICAgIC4uLnByZWRlY2Vzc29yVmVydGV4ZXMsXG4gICAgICAgICAgICBzb3VyY2VWZXJ0ZXhcbiAgICAgICAgICBdLFxuICAgICAgICAgIHZlcnRleE5hbWVzID0gdmVydGV4TmFtZXNGcm9tVmVydGV4ZXModmVydGV4ZXMpLFxuICAgICAgICAgIGN5Y2xlID0gbmV3IEN5Y2xlKHZlcnRleE5hbWVzKTtcblxuICAgIHJldHVybiBjeWNsZTtcbiAgfVxufVxuIiwiUmVhY3QuY3JlYXRlRWxlbWVudCJdLCJuYW1lcyI6WyJDeWNsZSIsInZlcnRleE5hbWVzIiwiZ2V0VmVydGV4TmFtZXMiLCJmcm9tU291cmNlVmVydGV4QW5kUHJlZGVjZXNzb3JWZXJ0ZXhlcyIsInNvdXJjZVZlcnRleCIsInByZWRlY2Vzc29yVmVydGV4ZXMiLCJ2ZXJ0ZXhlcyIsInZlcnRleE5hbWVzRnJvbVZlcnRleGVzIiwiY3ljbGUiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O2VBSXFCQTs7O3NCQUZtQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRXpCLElBQUEsQUFBTUEsc0JBQU47YUFBTUEsTUFDUEMsV0FBVztnQ0FESkQ7UUFFakIsSUFBSSxDQUFDQyxjQUFjQTs7a0JBRkZEOztZQUtuQkUsS0FBQUE7bUJBQUFBLFNBQUFBO2dCQUNFLE9BQU8sSUFBSSxDQUFDRDtZQUNkOzs7O1lBRU9FLEtBQUFBO21CQUFQLFNBQU9BLHVDQUF1Q0MsWUFBWSxFQUFFQyxtQkFBbUI7Z0JBQzdFLElBQU1DLFdBQVcsQUFDVCxxQkFBR0QsNEJBRE07b0JBRVREO2lCQUNELEdBQ0RILGNBQWNNLElBQUFBLGlDQUF3QkQsV0FDdENFLFFBQVEsSUFmR1IsTUFlT0M7Z0JBRXhCLE9BQU9PO1lBQ1Q7OztXQWxCbUJSIn0=