public void Save(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                throw new InvalidOperationException();
            }

            var fullPath = Path.GetFullPath(path);
            if (!Directory.Exists(Path.GetDirectoryName(fullPath)))
            {
                throw new InvalidOperationException();
            }

            var requiredLength = LargePageMemoryChunk.Builder()
                .ReserveAligned(FirstChildEdgeIndex)
                .ReserveAligned(EdgeToNodeIndex)
                .ReserveAligned(EdgeCharacters)
                .ReserveAligned(ReachableTerminalNodes)
                .ReserveAligned(WordCounts)
                .AllocationSize;

            using var stream = File.OpenWrite(path);
            if (stream == null)
            {
                throw new InvalidOperationException();
            }

            stream.WriteCompressed(requiredLength);
            stream.WriteCompressed(RootNodeIndex);
            stream.WriteSequentialCompressedToUshort(FirstChildEdgeIndex);
            stream.WriteCompressed(EdgeToNodeIndex);
            stream.WriteUtf8(EdgeCharacters);
            stream.WriteCompressed(ReachableTerminalNodes);
            stream.WriteCompressed(WordCounts);
        }
        public CompressedSparseRowPointerGraph(Stream stream)
        {
            var totalPageSize = stream.ReadCompressedULong();

            MemoryChunk = new LargePageMemoryChunk(totalPageSize);

            RootNodeIndex = stream.ReadCompressedInt();

            var firstChildEdgeIndexCount = stream.ReadCompressedUInt();

            FirstChildEdgeIndex = MemoryChunk.GetArrayAligned <uint>(firstChildEdgeIndexCount);
            stream.ReadSequentialCompressedUshortToUint(FirstChildEdgeIndex, firstChildEdgeIndexCount);

            var edgeToNodeIndexCount = stream.ReadCompressedUInt();

#if DEBUG
            for (var i = 0; i < firstChildEdgeIndexCount; i++)
            {
                Debug.Assert(FirstChildEdgeIndex[i] <= edgeToNodeIndexCount, $"{nameof(FirstChildEdgeIndex)} should not point past {edgeToNodeIndexCount}, but it's value at index {i} was {FirstChildEdgeIndex[i]}");
            }
#endif

            EdgeToNodeIndex = MemoryChunk.GetArrayAligned <int>(edgeToNodeIndexCount);
            stream.ReadCompressed(EdgeToNodeIndex, edgeToNodeIndexCount);

#if DEBUG
            for (var i = 0; i < edgeToNodeIndexCount; i++)
            {
                Debug.Assert(Math.Abs(EdgeToNodeIndex[i]) <= firstChildEdgeIndexCount, $"{nameof(EdgeToNodeIndex)} should not point past {firstChildEdgeIndexCount}, but it's value at index {i} was {EdgeToNodeIndex[i]}");
            }
#endif
            var byteLength = stream.ReadCompressedUInt();
            var charLength = stream.ReadCompressedUInt();
            EdgeCharacter = MemoryChunk.GetArrayAligned <char>(charLength);
            stream.ReadUtf8(EdgeCharacter, byteLength, charLength);

            var reachableTerminalNodesCount = stream.ReadCompressedUInt();
            ReachableTerminalNodes = MemoryChunk.GetArrayAligned <ushort>(reachableTerminalNodesCount);
            stream.ReadCompressed(ReachableTerminalNodes, reachableTerminalNodesCount);

            WordCount  = stream.ReadCompressedUInt();
            WordCounts = MemoryChunk.GetArrayAligned <ulong>(WordCount);
            stream.ReadCompressed(WordCounts, WordCount);

            MemoryChunk.Lock();
        }