GithubHelp home page GithubHelp logo

rvanvenetie / spacetime Goto Github PK

View Code? Open in Web Editor NEW
0.0 2.0 0.0 2.9 MB

This code supplements arXiv:2104.08143, where we describe an adaptive method for parabolic evolution equations.

License: MIT License

Python 0.17% CMake 1.26% C++ 98.57%
finite-element-methods sparse-grids adaptive-refinement parabolic-pde complexity

spacetime's People

Contributors

bla-zz avatar jannertje avatar rvanvenetie avatar

Watchers

 avatar  avatar

spacetime's Issues

Cleanup

Once the spacetime code is working, we need to cleanup this beast.

Optimizations

The spacetime applicator is very slow. Some parts can be optimized using simple caching, others require more thinking.

Specifically, the space applicator is slow because of creating triangulationviews, etc.

Total time: 47.4894 s
File: /Users/raymond/Projects/spacetime/Python/applications/heat_equation_test.py
Function: test_heat_error_reduction at line 226

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   226                                           @pytest.mark.slow
   227                                           @profile
   228                                           def test_heat_error_reduction():
   229         1          6.0      6.0      0.0      max_level = 10
   230                                               # Create space part.
   231         1        601.0    601.0      0.0      triang = InitialTriangulation.unit_square()
   232         1     428861.0 428861.0      0.9      triang.vertex_meta_root.uniform_refine(max_level + 2)
   233         1         71.0     71.0      0.0      basis_space = HierarchicalBasisFunction.from_triangulation(triang)
   234         1     476890.0 476890.0      1.0      basis_space.deep_refine()
   235
   236                                               # Create time part for X^\delta
   237         1          5.0      5.0      0.0      basis_time = ThreePointBasis()
   238         1     175187.0 175187.0      0.4      basis_time.metaroot_wavelet.uniform_refine(max_level)
   239
   240         1          6.0      6.0      0.0      n_t = 9
   241         1         23.0     23.0      0.0      errors_quad = []
   242         1          4.0      4.0      0.0      ndofs = []
   243         1          2.0      2.0      0.0      time_per_dof = []
   244         1         61.0     61.0      0.0      np.set_printoptions(precision=4)
   245         1         26.0     26.0      0.0      np.set_printoptions(linewidth=10000)
   246         1          6.0      6.0      0.0      for level in range(6, max_level, 2):
   247                                                   # Create X^\delta as a sparse grid.
   248         1          4.0      4.0      0.0          X_delta = DoubleTree.from_metaroots(
   249         1         69.0     69.0      0.0              (basis_time.metaroot_wavelet, basis_space.root))
   250         1     119463.0 119463.0      0.3          X_delta.sparse_refine(level, weights=[2, 1])
   251         1       5784.0   5784.0      0.0          ndofs.append(len(X_delta.bfs()))
   252         1          5.0      5.0      0.0          print('X_delta: dofs time axis={}\tdofs space axis={}'.format(
   253         1       3097.0   3097.0      0.0              len(X_delta.project(0).bfs()), len(X_delta.project(1).bfs())))
   254
   255                                                   # Create heat equation object.
   256         1     369420.0 369420.0      0.8          heat_eq = HeatEquation(X_delta=X_delta)
   257         1    1682806.0 1682806.0      3.5          rhs = example_rhs(heat_eq)
   258
   259                                                   # Now actually solve this beast!
   260         1   44227050.0 44227050.0     93.1          sol, num_iters = heat_eq.solve(rhs)
   261                                                   residual_norm = np.linalg.norm(
   262                                                       heat_eq.mat.apply(sol).to_array() - rhs.to_array())
   263                                                   print('MINRES solved in {} iterations with a residual norm {}'.format(
   264                                                       num_iters, residual_norm))
   265                                                   print('Time per dof is approximately {}'.format(
   266                                                       heat_eq.time_per_dof()))
   267                                                   time_per_dof.append(heat_eq.time_per_dof())
   268
   269                                                   u, u_order, u_slice_norm = example_solution_function()
   270
   271                                                   cur_errors_quad = np.ones(n_t)
   272                                                   for i, t in enumerate(np.linspace(0, 1, n_t)):
   273                                                       sol_slice = sol[1].slice(i=0,
   274                                                                                coord=t,
   275                                                                                slice_cls=TriangulationFunction)
   276                                                       cur_errors_quad[i] = sol_slice.error_L2(
   277                                                           lambda xy: u[0](t) * u[1](xy),
   278                                                           u_slice_norm(t),
   279                                                           u_order[1],
   280                                                       )
   281
   282                                                   errors_quad.append(cur_errors_quad)
   283                                                   rates_quad = np.log(errors_quad[-1] / errors_quad[0]) / np.log(
   284                                                       ndofs[0] / ndofs[-1])
   285
   286                                                   print('-- Results for level = {} --'.format(level))
   287                                                   print('\tdofs:', ndofs[-1])
   288                                                   print('\ttime_per_dof: {0:.4f}'.format(time_per_dof[-1]))
   289                                                   print('\terrors:', cur_errors_quad)
   290                                                   print('\trates:', rates_quad)
   291                                                   print('\n')

Total time: 43.9371 s
File: /Users/raymond/Projects/spacetime/Python/datastructures/applicator.py
Function: apply at line 151

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   151                                               @profile
   152                                               def apply(self, vec):
   153                                                   """ Applies this block-bilinear form the given input vectors.
   154
   155                                                   Arguments:
   156                                                       vec: (vec_0, vec_1) a block-vector on Z_0 x Z_1.
   157                                                   """
   158         8         15.0      1.9      0.0          assert isinstance(vec, BlockTreeVector)
   159         8    8112691.0 1014086.4     18.5          out_0 = self.applicators[0][0].apply(vec[0])
   160         8   15947856.0 1993482.0     36.3          out_0 += self.applicators[0][1].apply(vec[1])
   161
   162         8   13614890.0 1701861.2     31.0          out_1 = self.applicators[1][0].apply(vec[0])
   163         7    6261599.0 894514.1     14.3          out_1 += self.applicators[1][1].apply(vec[1])
   164
   165         7         82.0     11.7      0.0          return BlockTreeVector((out_0, out_1))

Total time: 44.2122 s
File: /Users/raymond/Projects/spacetime/Python/datastructures/applicator.py
Function: _matvec at line 194

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   194                                               @profile
   195                                               def _matvec(self, x):
   196         8         49.0      6.1      0.0          time_begin = time.process_time()
   197
   198         8     184770.0  23096.2      0.4          self.input_vec.from_array(x)
   199         8   43937257.0 5492157.1     99.4          res_tree = self.applicator.apply(self.input_vec)
   200         7      90048.0  12864.0      0.2          result = res_tree.to_array()
   201
   202         7         67.0      9.6      0.0          self.total_time += time.process_time() - time_begin
   203         7         15.0      2.1      0.0          self.total_applies += 1
   204         7          3.0      0.4      0.0          return result

Total time: 12.979 s
File: /Users/raymond/Projects/spacetime/Python/datastructures/multi_tree_view.py
Function: _refine at line 112

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   112                                               @profile
   113                                               def _refine(self,
   114                                                           i,
   115                                                           children=None,
   116                                                           call_filter=None,
   117                                                           make_conforming=False):
   118                                                   """ Refines the node in the `i`-th coordinate.
   119
   120                                                   If the node that will be introduced has multiple parents, then these
   121                                                   must be brothers of self (and already exist).
   122
   123                                                   Args:
   124                                                     i: The axis we are considering.
   125                                                     children: If set, the list of children to create. If none, refine
   126                                                               all children that exist in the underlying tree.
   127                                                     call_filter: This function can be used to filter children. It is
   128                                                       called with the multi-node that is to be created.
   129                                                   """
   130    349269    1118420.0      3.2      8.6          if self.is_full(i): return self._children[i]
   131    269287     269398.0      1.0      2.1          if children is None: children = self.nodes[i].children
   132    269287     258473.0      1.0      2.0          if call_filter is None: call_filter = lambda _: True
   133
   134    610224     594618.0      1.0      4.6          for child_i in children:
   135                                                       # If this child does not exist in underlying tree, we can stop.
   136    340938     543852.0      1.6      4.2              if child_i not in self.nodes[i].children: continue
   137
   138    340938     722020.0      2.1      5.6              child_nodes = _replace(i, self.nodes, child_i)
   139
   140                                                       # Skip if this child already exists, or if the filter doesn't pass.
   141    340938    1668732.0      4.9     12.9              if child_nodes in (n.nodes for n in self._children[i]) \
   142    279502     690630.0      2.5      5.3                      or not call_filter(child_nodes):
   143     98692      84711.0      0.9      0.7                  continue
   144
   145                                                       # Ensure all the parents of the to be created child exist.
   146                                                       # These are brothers in various axis of the current node.
   147    180810     166423.0      0.9      1.3              brothers = []
   148    458167     548074.0      1.2      4.2              for j in range(self.dim):
   149    277357     253668.0      0.9      2.0                  brothers.append([
   150    277357     291749.0      1.1      2.2                      self._find_brother(
   151                                                                   _replace(j, child_nodes, child_parent_j), j, i,
   152                                                                   make_conforming)
   153    277357    2204697.0      7.9     17.0                      for child_parent_j in child_nodes[j].parents
   154                                                           ])
   155
   156    180809    1785655.0      9.9     13.8              child = self.__class__(nodes=child_nodes, parents=brothers)
   157    458164     526327.0      1.1      4.1              for j in range(self.dim):
   158    585017     590267.0      1.0      4.5                  for brother in brothers[j]:
   159    307662     378087.0      1.2      2.9                      brother._children[j].append(child)
   160
   161    269286     283166.0      1.1      2.2          return self._children[i]

Total time: 7.12491 s
File: /Users/raymond/Projects/spacetime/Python/datastructures/multi_tree_view.py
Function: _deep_refine at line 234

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   234                                               @profile
   235                                               def _deep_refine(self, call_filter=None, call_postprocess=None):
   236                                                   """ Deep-refines `self` by recursively refining the multitree.
   237
   238                                                   Args:
   239                                                     call_filter: This call determines whether a given multinode
   240                                                       should be inside the subtree.
   241                                                     call_postprocess: This call will be invoked with a freshly
   242                                                         created multinode object. Can be used to load data, etc.
   243                                                   """
   244       875        958.0      1.1      0.0          if call_postprocess is None: call_postprocess = lambda _: None
   245       875        679.0      0.8      0.0          my_nodes = []
   246       875       1182.0      1.4      0.0          queue = deque([self])
   247     70274      46093.0      0.7      0.6          while queue:
   248     69399      52155.0      0.8      0.7              my_node = queue.popleft()
   249     69399      55415.0      0.8      0.8              if my_node.marked: continue
   250     64835      49957.0      0.8      0.7              my_nodes.append(my_node)
   251     64835     361059.0      5.6      5.1              call_postprocess(my_node)
   252     64835      49693.0      0.8      0.7              my_node.marked = True
   253    130190     125507.0      1.0      1.8              for i in range(self.dim):
   254     65355      41918.0      0.6      0.6                  queue.extend(
   255     65355      48026.0      0.7      0.7                      my_node._refine(i=i,
   256     65355      37710.0      0.6      0.5                                      call_filter=call_filter,
   257     65355    6167355.0     94.4     86.6                                      make_conforming=True))
   258
   259     65710      40240.0      0.6      0.6          for my_node in my_nodes:
   260     64835      46959.0      0.7      0.7              my_node.marked = False

Total time: 15.7844 s
File: /Users/raymond/Projects/spacetime/Python/space/applicator.py
Function: apply at line 19

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    19                                               @profile
    20                                               def apply(self, vec_in, vec_out):
    21                                                   """ Apply the multiscale operator.  """
    22      1447     392314.0    271.1      2.5          vec_in_nodes = vec_in.bfs()
    23      1447     285316.0    197.2      1.8          vec_out_nodes = vec_out.bfs()
    24      1447       2278.0      1.6      0.0          if len(vec_in_nodes) == 0 or len(vec_out_nodes) == 0: return
    25      1171      48860.0     41.7      0.3          if [n.node for n in vec_in_nodes] == [n.node for n in vec_out_nodes]:
    26                                                       # This is the case where vec_in == vec_out, i.e. symmetric.
    27       872    9116427.0  10454.6     57.8              self.operator.triang = TriangulationView(vec_in)
    28       872     305602.0    350.5      1.9              np_vec_in = vec_in.to_array()
    29       872    1310412.0   1502.8      8.3              np_vec_out = self.operator.apply(np_vec_in)
    30       872     304077.0    348.7      1.9              vec_out.from_array(np_vec_out)
    31       872       1056.0      1.2      0.0              return vec_out
    32
    33                                                   # This is the case where vec_in != vec_out.
    34                                                   # We can handle this by enlarging vec_in and vec_out.
    35       299        316.0      1.1      0.0          def call_copy(my_node, other_node):
    36                                                       if not my_node.is_metaroot():
    37                                                           my_node.value = other_node.value
    38
    39                                                   # If that's not the case, create an enlarged vec_in.
    40       299       9812.0     32.8      0.1          vec_in_new = TreeVector.from_metaroot(vec_in.node)
    41       299    1905307.0   6372.3     12.1          vec_in_new.union(vec_in, call_postprocess=call_copy)
    42       299     202843.0    678.4      1.3          vec_in_new.union(vec_out, call_postprocess=None)
    43
    44                                                   # Also create an enlarge vec_out.
    45       299       9849.0     32.9      0.1          vec_out_new = TreeVector.from_metaroot(vec_in.node)
    46       299    1393367.0   4660.1      8.8          vec_out_new.union(vec_in, call_postprocess=None)
    47       299     205388.0    686.9      1.3          vec_out_new.union(vec_out, call_postprocess=None)
    48
    49                                                   # Apply for these enlarged vectors.
    50       299        971.0      3.2      0.0          self.apply(vec_in_new, vec_out_new)
    51
    52                                                   # Now copy only specific parts back into vec_out, mark these nodes.
    53       299        553.0      1.8      0.0          vec_out.union(vec_out_new,
    54       299        348.0      1.2      0.0                        call_filter=lambda _: False,
    55       299     289023.0    966.6      1.8                        call_postprocess=call_copy)
    56
    57       299        294.0      1.0      0.0          return vec_out

Total time: 8.50112 s
File: /Users/raymond/Projects/spacetime/Python/space/triangulation_view.py
Function: __init__ at line 29

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    29                                               @profile
    30                                               def __init__(self, vertex_view):
    31                                                   """ Initializer given a vertex (sub)tree. """
    32       872       3041.0      3.5      0.0          if isinstance(vertex_view, NodeViewInterface):
    33       573       4883.0      8.5      0.1              assert vertex_view.is_metaroot()
    34
    35                                                   # Store the vertices inside the vertex_view
    36       872     233930.0    268.3      2.8          self.vertices = vertex_view.bfs()
    37       872       1367.0      1.6      0.0          assert self.vertices
    38
    39                                                   # In the case of stacked views, we must iterate to find the vertices.
    40      2616       7851.0      3.0      0.1          while isinstance(self.vertices[0], NodeViewInterface):
    41      1744      35780.0     20.5      0.4              self.vertices = [v.node for v in self.vertices]
    42       872       1262.0      1.4      0.0          assert isinstance(self.vertices[0], Vertex)
    43
    44                                                   # Extract the original element root.
    45       872       2565.0      2.9      0.0          elem_meta_root = self.vertices[0].patch[0].parent
    46       872       1217.0      1.4      0.0          assert isinstance(elem_meta_root, MetaRoot)
    47       872       1449.0      1.7      0.0          assert isinstance(elem_meta_root.children[0], Element2D)
    48
    49                                                   # Mark all necessary vertices -- uses the mark field inside vertex.
    50     22652      29109.0      1.3      0.3          for idx, vertex in enumerate(self.vertices):
    51     21780      34191.0      1.6      0.4              assert not vertex.is_metaroot()
    52     21780      27740.0      1.3      0.3              vertex.marked = idx
    53
    54                                                   # Two helper functions used inside the element tree generation..
    55       872       1206.0      1.4      0.0          def newest_vertex_in_tree_view(elem):
    56                                                       """ Does the given element have all vertices in our subtree. """
    57                                                       return not isinstance(elem.newest_vertex().marked, bool)
    58
    59       872       1149.0      1.3      0.0          def store_vertices_element_view(elem_view):
    60                                                       """ Store vertex view indices inside the element_view object. """
    61                                                       if not isinstance(elem_view.node, MetaRoot):
    62                                                           elem_view.vertices_view_idx = [
    63                                                               v.marked for v in elem_view.node.vertices
    64                                                           ]
    65
    66                                                   # Now create the associated element tree.
    67       872      19814.0     22.7      0.2          self.elem_tree_view = TreeView(ElementView(elem_meta_root))
    68       872       1505.0      1.7      0.0          self.elem_tree_view.deep_refine(
    69       872       1096.0      1.3      0.0              call_filter=newest_vertex_in_tree_view,
    70       872    7137981.0   8185.8     84.0              call_postprocess=store_vertices_element_view)
    71
    72                                                   # Unmark the vertices
    73     22652      27833.0      1.2      0.3          for vertex in self.vertices:
    74     21780      28033.0      1.3      0.3              vertex.marked = False
    75
    76                                                   # Also store a flattened list of the elements.
    77       872     364725.0    418.3      4.3          self.elements = self.elem_tree_view.bfs()
    78       872       1332.0      1.5      0.0          assert self.elements
    79
    80                                                   # Create the history object -- uses mark field of the vertex view obj.
    81       872       1240.0      1.4      0.0          self.history = []
    82     60072      74361.0      1.2      0.9          for elem in self.elements:
    83     59200     102877.0      1.7      1.2              vertex = self.vertices[elem.newest_vertex()]
    84     59200     186723.0      3.2      2.2              if elem.level == 0 or vertex.marked: continue
    85     18292      24101.0      1.3      0.3              vertex.marked = True
    86     18292      37074.0      2.0      0.4              assert len(elem.parents) == 1
    87     18292      45941.0      2.5      0.5              self.history.append((elem.newest_vertex(), elem.parents[0]))
    88
    89       872       1418.0      1.6      0.0          assert len(self.history) == len(self.vertices) - len(
    90       872       1891.0      2.2      0.0              self.vertices[0].parents[0].children)
    91
    92                                                   # Undo marking.
    93     22652      27865.0      1.2      0.3          for vertex in self.vertices:
    94     21780      28574.0      1.3      0.3              vertex.marked = False

Total time: 42.9673 s
File: /Users/raymond/Projects/spacetime/Python/spacetime/applicator.py
Function: apply at line 107

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
   107                                               @profile
   108                                               def apply(self, vec):
   109                                                   """ Apply the tensor product applicator to the given vector. """
   110                                                   # Assert that vec is defined on Lambda_in
   111        46         62.0      1.3      0.0          assert all(n1.nodes == n2.nodes
   112        46     453630.0   9861.5      1.1                     for n1, n2 in zip(vec.bfs(), self.Lambda_in.bfs()))
   113
   114                                                   # Shortcut for clarity.
   115        46         95.0      2.1      0.0          vec_in = vec
   116
   117                                                   # Create two empty out vectors for the L and U part.
   118        46    3492560.0  75925.2      8.1          vec_out_low = self.Lambda_out.deep_copy(mlt_tree_cls=DoubleTreeVector)
   119        46    3768375.0  81921.2      8.8          vec_out_upp = self.Lambda_out.deep_copy(mlt_tree_cls=DoubleTreeVector)
   120
   121                                                   # Calculate R_sigma(Id x A_1)I_Lambda
   122        46    3763938.0  81824.7      8.8          sigma = self.sigma()
   123       621       8795.0     14.2      0.0          for psi_in_labda in sigma.project(0).bfs():
   124       575      15097.0     26.3      0.0              fiber_in = vec_in.fiber(1, psi_in_labda)
   125       575       2605.0      4.5      0.0              fiber_out = sigma.fiber(1, psi_in_labda)
   126       575    9698613.0  16867.2     22.6              self.applicator_space.apply(fiber_in, fiber_out)
   127
   128                                                   # Calculate R_Lambda(L_0 x Id)I_Sigma
   129     13340     182604.0     13.7      0.4          for psi_out_labda in vec_out_low.project(1).bfs():
   130     13294      73938.0      5.6      0.2              fiber_in = sigma.fiber(0, psi_out_labda)
   131     13294     294367.0     22.1      0.7              fiber_out = vec_out_low.fiber(0, psi_out_labda)
   132     13294    2951703.0    222.0      6.9              self.applicator_time.apply_low(fiber_in, fiber_out)
   133
   134                                                   # Calculate R_Theta(U_1 x Id)I_Lambda
   135        46    6772692.0 147232.4     15.8          theta = self.theta()
   136     13050     173861.0     13.3      0.4          for psi_in_labda in theta.project(1).bfs():
   137     13005      71519.0      5.5      0.2              fiber_in = vec_in.fiber(0, psi_in_labda)
   138     13005      56701.0      4.4      0.1              fiber_out = theta.fiber(0, psi_in_labda)
   139     13005    3593731.0    276.3      8.4              self.applicator_time.apply_upp(fiber_in, fiber_out)
   140
   141                                                   # Calculate R_Lambda(id x A2)I_Theta
   142       618       8549.0     13.8      0.0          for psi_out_labda in vec_out_upp.project(0).bfs():
   143       573       4308.0      7.5      0.0              fiber_in = theta.fiber(1, psi_out_labda)
   144       573     224749.0    392.2      0.5              fiber_out = vec_out_upp.fiber(1, psi_out_labda)
   145       573    6116572.0  10674.6     14.2              self.applicator_space.apply(fiber_in, fiber_out)
   146
   147                                                   # Sum and return the results.
   148        45         35.0      0.8      0.0          vec_out = vec_out_low
   149        45    1238111.0  27513.6      2.9          vec_out += vec_out_upp
   150        45         50.0      1.1      0.0          return vec_out

Total time: 3.63263 s
File: /Users/raymond/Projects/spacetime/Python/time/applicator.py
Function: _initialize at line 30

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    30                                               @profile
    31                                               def _initialize(self, vec_in, vec_out):
    32                                                   """ Helper function to initialize fields in datastructures. """
    33
    34                                                   # Sanity check that we start with an empty vector
    35     26299      45464.0      1.7      1.3          if isinstance(vec_out, NodeViewInterface):
    36     49242     711460.0     14.4     19.6              for nv in vec_out.bfs():
    37     22943      28016.0      1.2      0.8                  assert nv.value == 0
    38                                                   else:
    39                                                       for psi, value in vec_out.items():
    40                                                           assert value == 0
    41
    42     26299     941108.0     35.8     25.9          self.Lambda_in = MultiscaleFunctions(vec_in)
    43     26299    1029677.0     39.2     28.3          self.Lambda_out = MultiscaleFunctions(vec_out)
    44
    45                                                   # Store the vector inside the wavelet tree.
    46     26299      47417.0      1.8      1.3          if isinstance(vec_in, NodeViewInterface):
    47     41015     585308.0     14.3     16.1              for nv in vec_in.bfs():
    48     14716      24364.0      1.7      0.7                  assert nv.node.coeff[0] == 0
    49     14716      30279.0      2.1      0.8                  nv.node.coeff[0] = nv.value
    50                                                   else:
    51                                                       for psi, value in vec_in.items():
    52                                                           assert psi.coeff[0] == 0
    53                                                           psi.coeff[0] = value
    54
    55                                                   # Last, update the fields inside the elements.
    56     41015      50624.0      1.2      1.4          for psi in self.Lambda_in:
    57     35708      23789.0      0.7      0.7              for elem in psi.support:
    58     20992      13312.0      0.6      0.4                  elem.Lambda_in = True
    59
    60     49242      46477.0      0.9      1.3          for psi in self.Lambda_out:
    61     55893      35569.0      0.6      1.0              for elem in psi.support:
    62     32950      19762.0      0.6      0.5                  elem.Lambda_out = True

Total time: 1.12214 s
File: /Users/raymond/Projects/spacetime/Python/time/applicator.py
Function: _finalize at line 64

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    64                                               @profile
    65                                               def _finalize(self, vec_in, vec_out):
    66                                                   """ Helper function to finalize the results.
    67
    68                                                   This also copies the data from the single trees into vec_out. """
    69
    70                                                   # Copy result into vec_out
    71     26299      56793.0      2.2      5.1          if isinstance(vec_out, NodeViewInterface):
    72     49242     709730.0     14.4     63.2              for nv in vec_out.bfs():
    73     22943      51744.0      2.3      4.6                  nv.value = nv.node.coeff[1]
    74                                                   else:
    75                                                       # TODO: This should be removed
    76                                                       for psi in self.Lambda_out:
    77                                                           vec_out[psi] = psi.coeff[1]
    78
    79                                                   # Delete the used fields in the Element
    80     41015      47416.0      1.2      4.2          for psi in self.Lambda_in:
    81     35708      21925.0      0.6      2.0              for elem in psi.support:
    82     20992      12222.0      0.6      1.1                  elem.Lambda_in = False
    83     49242      43732.0      0.9      3.9          for psi in self.Lambda_out:
    84     55893      32763.0      0.6      2.9              for elem in psi.support:
    85     32950      18317.0      0.6      1.6                  elem.Lambda_out = False
    86
    87                                                   # Delete the used fields in the input/output vector
    88     49242      42550.0      0.9      3.8          for psi in self.Lambda_out:
    89     22943      28862.0      1.3      2.6              psi.reset_coeff()
    90     41015      39436.0      1.0      3.5          for psi in self.Lambda_in:
    91     14716      16647.0      1.1      1.5              psi.reset_coeff()

Spacetime doubletrees: figure out the correct input/output

Right now we have some double tree living in the spacetime code. At some point you want to invoke the applicator on space/time, which is probably invoked on a fiber/projection. This was/is implemented as a FrozenDoubleNode. So we need a way to make this compatible with the NodeView object. Few options that come to mind:

  1. Make a common NodeViewInterface and make FrozenDoubleNode/NodeView implement this;
  2. Somehow make FrozenDoubleNode a NodeView
  3. Create a way to copy a FrozenDoubleNode into a NodeView.

Restructure tree classes.

Most of our constructions are actually trees. We should create a unified OOP setting to incorporate these details.

Multilevel space

To implement the multilevel operator in space efficiently, we somehow must design our algorithm to store the matrix operations in the vertex-tree.

Space triangulation code needs a revamp

The triangulation object is now intertwined with the Element object code. This could be seperated, i.e., we could get rid of the Triangulation object and do everything locally in the Element class.

Orthonormal discontinuous pw linear wavelet basis mass matrix is not the identity

We know that for this basis, the mass matrix
$$ M := \langle \Psi_\Lambda, \Psi_\Lambda \rangle =: \begin{bmatrix} \int \psi_\lambda \psi_\mu dt \end{bmatrix}_{\lambda,\mu \in \Lambda} $$
should be the identity matrix for any $\Lambda$.

If we take $\Lambda$ the uniform grid, then this works (cf applicator_test.py:test_orthonormal_multiscale_mass). However, taking
$$ \Lambda := { (0,0), (0,1), (1,0), (1,1), (2,0), (2,1) } $$
the results are wrong :'(

Cleanup NodeView and DoubleTree

Currently, there is a lot of duplicate code in the classes NodeView and DoubleTree. This is because both classes define views on underlying trees.

A way to unify this would be to introduce a MultiTreeView, a view object that represents a multitree with multiple axes. Then DoubleTree would be an instance with 2 axes, whereas NodeView would be an instance with 1 axis.

Not sure if it's worth the trouble of implementing this, but it seems like it would shorten code.

Spacetime doubletree vectors

We should also have a way to store vectors on the spacetime doubletrees. Not sure if this requires a DoubleNodeView, or that we can simply add a coefficient field to DoubleNode...

NodeVector

Extend the NodeView class to hold a vector coefficient. This will allow to define vectors on (sub)trees.

While at it, you can implement a plus operator. This will be useful.

Revist the time applicator_inplace

In the current implementation of applicator_inplace vectors are stored directly on the wavelet/scaling trees. We need to figure out a way to get around this (NodeVector (?)), or carefully clean up these coefficients after using them.

The problem is that we have coefficients op 4 different trees: scaling / wavelet for basis_in and scaling / wavelet for basis_out. In a simple nodevector these couplings are lost..

Damping matrix evalA unequal to damping matrix evalU + evalL

The setting is as follows: we have the orthonormal discontinuous piecewise linear wavelet basis from figure 2. We want to apply the damping matrix $C_{\mu,\lambda} := \int_0^1 \phi_{\mu} [\partial_t \phi_{\lambda}] d t$, as it is a more interesting operator than the mass matrix (which is the identity by definition of this basis).

One can verify numerically (see Mathematica/evalA.nb in the git) that the singlescale damping matrix at level 0 equals $C^{ss}_0 = \begin{bmatrix} 0 & 2 \sqrt{3} \\ 0 & 0 \end{bmatrix}$.

Moreover, one can verify using Mathematica (see Mathematica/evalA.nb in the git) that for $\ell=1$, the multiscale damping matrix satisfies $C^{ms}_1 = \begin{bmatrix} 0 & 2 \sqrt{3} & -6 & 0 \\ 0 & 0 & 0 & 6 \\ 0 & 0 & 0 & 2 \sqrt{3} \\0 & 0 & 0 & 0 \end{bmatrix}$.

If we compute $C^{ms}_1$ as $U_1 + L_1$ (Applicator::apply_upp + Applicator::apply_low), we retrieve this matrix. However, if we compute it as $C^{ms}_1$ directly (Applicator::apply), we get the incorrect matrix $\begin{bmatrix} 0 & 2 * 2 \sqrt{3} & -6 & 0 \\ 0 & 0 & 0 & 6 \\ 0 & 0 & 0 & 2 \sqrt{3} \\0 & 0 & 0 & 0 \end{bmatrix}$.

For higher levels, $U_\ell + L_\ell$ retrieves the correct matrix but $C^{ms}_\ell$ directly seems to overshoot the nonzero elements by a factor $\ell - |\lambda|$ or something, very peculiar. Run python/applicator_test.py:test_orthonormal_multiscale_damping_equivalent to see.

Revisit parent-child relation for orthonormal basis

In all of our code, a refine step creates all possible children of the current node, except in the orthonormal basis, where a red node creates just red children, and a black node just black children.

This is ok but not really consistent.
See also the next issue.

Apply the actual system matrix

We have need to apply the following block matrix:
image

We've got all the components of the block matrix. So two things need to be done:

  • A way to apply this system matrix as a whole
  • A way to calculate/represent the right hand side of this system.

Inconsistency in evalA relative to [KS2012]

In the paper entitled "Fast evaluation of system matrix wrt multi-tree collections of tensor product refinable basis functions", section 2.2 shows evalA. We have evalA in section 3.1 of "Bla Parabolic".

In Bla Parabolic, we define a collection $\Pi_B$, which corresponds with $\hat \Pi^{(2)}$ in [KS2012]. Convince yourself that our $\Pi_B$ possibly contains more elements than theirs.

I originally implemented their version, which introduced problems for the 3-point hierarchical basis on nonuniform grids.

Moreover, no equivalent $\Pi_B$ is defined in evalU + evalL, which makes me wonder if that could be one cause for the problem that evalU+evalL != evalA. Bottom line is that I still don't fully understand the role of $\Pi_B$....

Orthonormal discontinuous pw linear wavelet basis is wrong?

In Figure 2 from the followup document, a wavelet basis is depicted. Left part shows the singlescale functions, right part the wavelets. The support of the wavelet functions is of size $2^{-\ell}$. I think this should be $2^{1-\ell}$, i.e., twice as wide. I will try to explain why.

For every $\ell$, we have a singlescale basis $\Phi_\ell$ spanned by all rescaled scaling functions on level $\ell$. The accompanying multiscale basis spans the same space, and is made up of basis functions $\Phi_0 \cup \Psi_1 \cup \cdots \cup \Psi_\ell$.
For $\ell = 1$, we compare $\Phi_1$ (being the space with four basis functions, one set on $[0,1/2]$ the other on $[1/2, 1]$) with $\Phi_0 \cup \Psi_1$. Now, $\Phi_0$ has two elements, and hence $\Psi_1$ should as well. So $\Psi_1$ should be the wavelet functions on $[0,1]$, not on $[0, 1/2]$ and $[1/2, 1]$ as indicated by the figure.

Experiment with a C++ backend

Our hypothesis is that Python objects are quite slow, and we are making a lot of them constantly (tree nodes, views on trees, etc). It would be nice to experiment with a C++ implementation of, let's say, these tree objects (basically the majority of the datastructures/ directory) which we could then load into Python to use in applicators.

It seems reasonable to try this out with, say, in C++ the time/space tree and a view on this tree, and in Python the corresponding applicator.
As for this C++/Python interface, good candidates seem to by Cppyy and pybind11.

Quadruature spacetime test

It would be very cool to test the spacetime applicator against quadrature. Not sure how this will work though.

Stateful operators

Right now the appliactor code is written in a stateless way, this is annoying for a few reasons:

  1. Hard to cache (expensive) calculations, for example TriangulationView objects are recreated the whole time.
  2. Some operators actually require a state to be correctly executed, e.g., all space applicators require a triangulation object to be executed, in the preconditioner the space applicators even depend on the corresponding time wavelet.

We should redesign the applicator/operators to allow for more stateful objects.

NodeView.copy()

Create a method that copies an entire nodeview tree. Not sure if really necessary, but might come in handy.

Spacetime bilinear form implementation of \gamma_0' \gamma_0.

Currently, the operator \gamma_0' \gamma_0 is implemented using a spacetime bilinear form, which means that its implementation requires sigma/theta. I think we could avoid this by creating a custom implementation of the \gamma_0' \gamma_0, or by creating way smaller versions of sigma\theta.

Not sure if its worth the engineering effort.

Extensions: Multithreading

To make our spacetime bilinearform multithreading friendly, there is a problem. The current code interacts with the underlying trees at some to evaluate the time applicator, or create the TriangulationView.

A possible solution would be to replicate these fields across all the threads. If I'm not mistaken, the only two fields that need to be replicated are the bool marked and void *data insidedatastrucutres/tree.hpp. These can be replicated, and then the corresponding functions have to be changed to use omp_get_thread_num() for correct indexing.

Also, on the time side, inside a time applicate, the underlying time trees can grow. That is, prolongation/mass operators refine certain nodes. This is of course also thread unsafe. A possible fix would be adding a mutex to every node, and doing a refine method inside a guarded block.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.