5
5
6
6
from scipy .sparse import (
7
7
diags ,
8
+ spdiags ,
8
9
eye ,
9
10
kron ,
10
11
csr_matrix ,
@@ -248,6 +249,8 @@ def integral(self, child, discretised_child):
248
249
else :
249
250
out = integration_vector @ discretised_child
250
251
252
+ out .copy_domains (child )
253
+
251
254
return out
252
255
253
256
def definite_integral_matrix (self , domain , vector_type = "row" ):
@@ -296,15 +299,19 @@ def definite_integral_matrix(self, domain, vector_type="row"):
296
299
matrix = csr_matrix (kron (eye (second_dim_len ), vector ))
297
300
return pybamm .Matrix (matrix )
298
301
299
- def indefinite_integral (self , child , discretised_child ):
302
+ def indefinite_integral (self , child , discretised_child , direction ):
300
303
"""Implementation of the indefinite integral operator. """
301
304
302
305
# Different integral matrix depending on whether the integrand evaluates on
303
306
# edges or nodes
304
307
if child .evaluates_on_edges ():
305
- integration_matrix = self .indefinite_integral_matrix_edges (child .domain )
308
+ integration_matrix = self .indefinite_integral_matrix_edges (
309
+ child .domain , direction
310
+ )
306
311
else :
307
- integration_matrix = self .indefinite_integral_matrix_nodes (child .domain )
312
+ integration_matrix = self .indefinite_integral_matrix_nodes (
313
+ child .domain , direction
314
+ )
308
315
309
316
# Don't need to check for spherical domains as spherical polars
310
317
# only change the diveregence (childs here have grad and no div)
@@ -314,10 +321,28 @@ def indefinite_integral(self, child, discretised_child):
314
321
315
322
return out
316
323
317
- def indefinite_integral_matrix_edges (self , domain ):
324
+ def indefinite_integral_matrix_edges (self , domain , direction ):
318
325
"""
319
326
Matrix for finite-volume implementation of the indefinite integral where the
320
- integrand is evaluated on mesh edges
327
+ integrand is evaluated on mesh edges (shape (n+1, 1)).
328
+ The integral will then be evaluated on mesh nodes (shape (n, 1)).
329
+
330
+ Parameters
331
+ ----------
332
+ domain : list
333
+ The domain(s) of integration
334
+ direction : str
335
+ The direction of integration (forward or backward). See notes.
336
+
337
+ Returns
338
+ -------
339
+ :class:`pybamm.Matrix`
340
+ The finite volume integral matrix for the domain
341
+
342
+ Notes
343
+ -----
344
+
345
+ **Forward integral**
321
346
322
347
.. math::
323
348
F(x) = \\ int_0^x\\ !f(u)\\ ,du
@@ -335,16 +360,81 @@ def indefinite_integral_matrix_edges(self, domain):
335
360
Hence we must have
336
361
337
362
- :math:`F_0 = du_{1/2} * f_{1/2} / 2`
338
- - :math:`F_{i+1} = F_i + du * f_{i+1/2}`
363
+ - :math:`F_{i+1} = F_i + du_{i+1/2} * f_{i+1/2}`
364
+
365
+ Note that :math:`f_{-1/2}` and :math:`f_{end+1/2}` are included in the discrete
366
+ integrand vector `f`, so we add a column of zeros at each end of the
367
+ indefinite integral matrix to ignore these.
368
+
369
+ **Backward integral**
370
+
371
+ .. math::
372
+ F(x) = \\ int_x^end\\ !f(u)\\ ,du
339
373
340
- Note that :math:`f_{-1/2}` and :math:`f_{n+1/2}` are included in the discrete
374
+ The indefinite integral must satisfy the following conditions:
375
+
376
+ - :math:`F(end) = 0`
377
+ - :math:`f(x) = -\\ frac{dF}{dx}`
378
+
379
+ or, in discrete form,
380
+
381
+ - `BoundaryValue(F, "right") = 0`, i.e. :math:`3*F_{end} - F_{end-1} = 0`
382
+ - :math:`f_{i+1/2} = -(F_{i+1} - F_i) / dx_{i+1/2}`
383
+
384
+ Hence we must have
385
+
386
+ - :math:`F_{end} = du_{end+1/2} * f_{end-1/2} / 2`
387
+ - :math:`F_{i-1} = F_i + du_{i-1/2} * f_{i-1/2}`
388
+
389
+ Note that :math:`f_{-1/2}` and :math:`f_{end+1/2}` are included in the discrete
341
390
integrand vector `f`, so we add a column of zeros at each end of the
342
391
indefinite integral matrix to ignore these.
392
+ """
393
+
394
+ # Create appropriate submesh by combining submeshes in domain
395
+ submesh_list = self .mesh .combine_submeshes (* domain )
396
+ submesh = submesh_list [0 ]
397
+ n = submesh .npts
398
+ sec_pts = len (submesh_list )
399
+
400
+ du_n = submesh .d_nodes
401
+ if direction == "forward" :
402
+ du_entries = [du_n ] * (n - 1 )
403
+ offset = - np .arange (1 , n , 1 )
404
+ main_integral_matrix = spdiags (du_entries , offset , n , n - 1 )
405
+ bc_offset_matrix = lil_matrix ((n , n - 1 ))
406
+ bc_offset_matrix [:, 0 ] = du_n [0 ] / 2
407
+ elif direction == "backward" :
408
+ du_entries = [du_n ] * (n + 1 )
409
+ offset = np .arange (n , - 1 , - 1 )
410
+ main_integral_matrix = spdiags (du_entries , offset , n , n - 1 )
411
+ bc_offset_matrix = lil_matrix ((n , n - 1 ))
412
+ bc_offset_matrix [:, - 1 ] = du_n [- 1 ] / 2
413
+ sub_matrix = main_integral_matrix + bc_offset_matrix
414
+ # add a column of zeros at each end
415
+ zero_col = csr_matrix ((n , 1 ))
416
+ sub_matrix = hstack ([zero_col , sub_matrix , zero_col ])
417
+ # Convert to csr_matrix so that we can take the index (row-slicing), which is
418
+ # not supported by the default kron format
419
+ # Note that this makes column-slicing inefficient, but this should not be an
420
+ # issue
421
+ matrix = csr_matrix (kron (eye (sec_pts ), sub_matrix ))
422
+
423
+ return pybamm .Matrix (matrix )
424
+
425
+ def indefinite_integral_matrix_nodes (self , domain , direction ):
426
+ """
427
+ Matrix for finite-volume implementation of the (backward) indefinite integral
428
+ where the integrand is evaluated on mesh nodes (shape (n, 1)).
429
+ The integral will then be evaluated on mesh edges (shape (n+1, 1)).
430
+ This is just a straightforward (backward) cumulative sum of the integrand
343
431
344
432
Parameters
345
433
----------
346
434
domain : list
347
435
The domain(s) of integration
436
+ direction : str
437
+ The direction of integration (forward or backward)
348
438
349
439
Returns
350
440
-------
@@ -358,16 +448,13 @@ def indefinite_integral_matrix_edges(self, domain):
358
448
n = submesh .npts
359
449
sec_pts = len (submesh_list )
360
450
361
- du_n = submesh .d_nodes
362
- du_entries = [du_n ] * (n - 1 )
363
- offset = - np .arange (1 , n , 1 )
364
- main_integral_matrix = diags (du_entries , offset , shape = (n , n - 1 ))
365
- bc_offset_matrix = lil_matrix ((n , n - 1 ))
366
- bc_offset_matrix [:, 0 ] = du_n [0 ] / 2
367
- sub_matrix = main_integral_matrix + bc_offset_matrix
368
- # add a column of zeros at each end
369
- zero_col = csr_matrix ((n , 1 ))
370
- sub_matrix = hstack ([zero_col , sub_matrix , zero_col ])
451
+ du_n = submesh .d_edges
452
+ du_entries = [du_n ] * n
453
+ if direction == "forward" :
454
+ offset = - np .arange (1 , n + 1 , 1 ) # from -1 down to -n
455
+ elif direction == "backward" :
456
+ offset = np .arange (n - 1 , - 1 , - 1 ) # from n-1 down to 0
457
+ sub_matrix = spdiags (du_entries , offset , n + 1 , n )
371
458
# Convert to csr_matrix so that we can take the index (row-slicing), which is
372
459
# not supported by the default kron format
373
460
# Note that this makes column-slicing inefficient, but this should not be an
@@ -473,41 +560,6 @@ def internal_neumann_condition(
473
560
474
561
return dy / dx
475
562
476
- def indefinite_integral_matrix_nodes (self , domain ):
477
- """
478
- Matrix for finite-volume implementation of the indefinite integral where the
479
- integrand is evaluated on mesh nodes.
480
- This is just a straightforward cumulative sum of the integrand
481
-
482
- Parameters
483
- ----------
484
- domain : list
485
- The domain(s) of integration
486
-
487
- Returns
488
- -------
489
- :class:`pybamm.Matrix`
490
- The finite volume integral matrix for the domain
491
- """
492
-
493
- # Create appropriate submesh by combining submeshes in domain
494
- submesh_list = self .mesh .combine_submeshes (* domain )
495
- submesh = submesh_list [0 ]
496
- n = submesh .npts
497
- sec_pts = len (submesh_list )
498
-
499
- du_n = submesh .d_edges
500
- du_entries = [du_n ] * (n )
501
- offset = - np .arange (1 , n + 1 , 1 )
502
- sub_matrix = diags (du_entries , offset , shape = (n + 1 , n ))
503
- # Convert to csr_matrix so that we can take the index (row-slicing), which is
504
- # not supported by the default kron format
505
- # Note that this makes column-slicing inefficient, but this should not be an
506
- # issue
507
- matrix = csr_matrix (kron (eye (sec_pts ), sub_matrix ))
508
-
509
- return pybamm .Matrix (matrix )
510
-
511
563
def add_ghost_nodes (self , symbol , discretised_symbol , bcs ):
512
564
"""
513
565
Add ghost nodes to a symbol.
0 commit comments