|
1 | 1 | from collections.abc import Callable |
| 2 | +from typing import List |
2 | 3 |
|
3 | 4 | from mpi4py import MPI |
4 | 5 |
|
@@ -1471,3 +1472,225 @@ def create_formulation(self): |
1471 | 1472 | entity_maps=entity_maps, |
1472 | 1473 | ) |
1473 | 1474 | self.J = dolfinx.fem.form(J, entity_maps=entity_maps) |
| 1475 | + |
| 1476 | + |
| 1477 | +class HydrogenTransportProblemDiscontinuousChangeVar(HydrogenTransportProblem): |
| 1478 | + species: List[_species.Species] |
| 1479 | + |
| 1480 | + def create_formulation(self): |
| 1481 | + """Creates the formulation of the model""" |
| 1482 | + |
| 1483 | + self.formulation = 0 |
| 1484 | + |
| 1485 | + # add diffusion and time derivative for each species |
| 1486 | + for spe in self.species: |
| 1487 | + u = spe.solution |
| 1488 | + u_n = spe.prev_solution |
| 1489 | + v = spe.test_function |
| 1490 | + |
| 1491 | + for vol in self.volume_subdomains: |
| 1492 | + D = vol.material.get_diffusion_coefficient( |
| 1493 | + self.mesh.mesh, self.temperature_fenics, spe |
| 1494 | + ) |
| 1495 | + if spe.mobile: |
| 1496 | + K_S = vol.material.get_solubility_coefficient( |
| 1497 | + self.mesh.mesh, self.temperature_fenics, spe |
| 1498 | + ) |
| 1499 | + c = u * K_S |
| 1500 | + c_n = u_n * K_S |
| 1501 | + else: |
| 1502 | + c = u |
| 1503 | + c_n = u_n |
| 1504 | + if spe.mobile: |
| 1505 | + self.formulation += ufl.dot(D * ufl.grad(c), ufl.grad(v)) * self.dx( |
| 1506 | + vol.id |
| 1507 | + ) |
| 1508 | + |
| 1509 | + if self.settings.transient: |
| 1510 | + self.formulation += ((c - c_n) / self.dt) * v * self.dx(vol.id) |
| 1511 | + |
| 1512 | + for reaction in self.reactions: |
| 1513 | + if isinstance(reaction.product, list): |
| 1514 | + products = reaction.product |
| 1515 | + else: |
| 1516 | + products = [reaction.product] |
| 1517 | + |
| 1518 | + # hack enforce the concentration attribute of the species for all species |
| 1519 | + # to be used in reaction.reaction_term |
| 1520 | + |
| 1521 | + for spe in self.species: |
| 1522 | + if spe.mobile: |
| 1523 | + K_S = reaction.volume.material.get_solubility_coefficient( |
| 1524 | + self.mesh.mesh, self.temperature_fenics, spe |
| 1525 | + ) |
| 1526 | + assert isinstance(spe, _species.SpeciesChangeVar) |
| 1527 | + spe.concentration = spe.solution * K_S |
| 1528 | + |
| 1529 | + # reactant |
| 1530 | + for reactant in reaction.reactant: |
| 1531 | + if isinstance(reactant, festim.species.Species): |
| 1532 | + self.formulation += ( |
| 1533 | + reaction.reaction_term(self.temperature_fenics) |
| 1534 | + * reactant.test_function |
| 1535 | + * self.dx(reaction.volume.id) |
| 1536 | + ) |
| 1537 | + |
| 1538 | + # product |
| 1539 | + for product in products: |
| 1540 | + self.formulation += ( |
| 1541 | + -reaction.reaction_term(self.temperature_fenics) |
| 1542 | + * product.test_function |
| 1543 | + * self.dx(reaction.volume.id) |
| 1544 | + ) |
| 1545 | + # add sources |
| 1546 | + for source in self.sources: |
| 1547 | + self.formulation -= ( |
| 1548 | + source.value.fenics_object |
| 1549 | + * source.species.test_function |
| 1550 | + * self.dx(source.volume.id) |
| 1551 | + ) |
| 1552 | + |
| 1553 | + # add fluxes |
| 1554 | + for bc in self.boundary_conditions: |
| 1555 | + if isinstance(bc, boundary_conditions.ParticleFluxBC): |
| 1556 | + self.formulation -= ( |
| 1557 | + bc.value_fenics |
| 1558 | + * bc.species.test_function |
| 1559 | + * self.ds(bc.subdomain.id) |
| 1560 | + ) |
| 1561 | + |
| 1562 | + # check if each species is defined in all volumes |
| 1563 | + if not self.settings.transient: |
| 1564 | + for spe in self.species: |
| 1565 | + # if species mobile, already defined in diffusion term |
| 1566 | + if not spe.mobile: |
| 1567 | + not_defined_in_volume = self.volume_subdomains.copy() |
| 1568 | + for vol in self.volume_subdomains: |
| 1569 | + # check reactions |
| 1570 | + for reaction in self.reactions: |
| 1571 | + if ( |
| 1572 | + spe in reaction.product |
| 1573 | + ): # TODO we probably need this in HydrogenTransportProblem too no? |
| 1574 | + if vol == reaction.volume: |
| 1575 | + if vol in not_defined_in_volume: |
| 1576 | + not_defined_in_volume.remove(vol) |
| 1577 | + |
| 1578 | + # add c = 0 to formulation where needed |
| 1579 | + for vol in not_defined_in_volume: |
| 1580 | + self.formulation += ( |
| 1581 | + spe.solution * spe.test_function * self.dx(vol.id) |
| 1582 | + ) |
| 1583 | + |
| 1584 | + def initialise_exports(self): |
| 1585 | + self.override_post_processing_solution() |
| 1586 | + super().initialise_exports() |
| 1587 | + |
| 1588 | + def override_post_processing_solution(self): |
| 1589 | + # override the post-processing solution c = theta * K_S |
| 1590 | + Q0 = fem.functionspace(self.mesh.mesh, ("DG", 0)) |
| 1591 | + Q1 = fem.functionspace(self.mesh.mesh, ("DG", 1)) |
| 1592 | + |
| 1593 | + for spe in self.species: |
| 1594 | + if not spe.mobile: |
| 1595 | + continue |
| 1596 | + K_S0 = fem.Function(Q0) |
| 1597 | + E_KS = fem.Function(Q0) |
| 1598 | + for subdomain in self.volume_subdomains: |
| 1599 | + entities = subdomain.locate_subdomain_entities_correct( |
| 1600 | + self.volume_meshtags |
| 1601 | + ) |
| 1602 | + K_S0.x.array[entities] = subdomain.material.get_K_S_0(spe) |
| 1603 | + E_KS.x.array[entities] = subdomain.material.get_E_K_S(spe) |
| 1604 | + |
| 1605 | + K_S = K_S0 * ufl.exp(-E_KS / (festim.k_B * self.temperature_fenics)) |
| 1606 | + |
| 1607 | + theta = spe.solution |
| 1608 | + |
| 1609 | + spe.dg_expr = fem.Expression( |
| 1610 | + theta * K_S, get_interpolation_points(Q1.element) |
| 1611 | + ) |
| 1612 | + spe.post_processing_solution = fem.Function(Q1) |
| 1613 | + spe.post_processing_solution.interpolate( |
| 1614 | + spe.dg_expr |
| 1615 | + ) # NOTE: do we need this line since it's in initialise? |
| 1616 | + |
| 1617 | + def post_processing(self): |
| 1618 | + # need to compute c = theta * K_S |
| 1619 | + # this expression is stored in species.dg_expr |
| 1620 | + for spe in self.species: |
| 1621 | + if not spe.mobile: |
| 1622 | + continue |
| 1623 | + spe.post_processing_solution.interpolate(spe.dg_expr) |
| 1624 | + |
| 1625 | + super().post_processing() |
| 1626 | + |
| 1627 | + def create_dirichletbc_form(self, bc: festim.FixedConcentrationBC): |
| 1628 | + """Creates a dirichlet boundary condition form |
| 1629 | +
|
| 1630 | + Args: |
| 1631 | + bc (festim.DirichletBC): the boundary condition |
| 1632 | +
|
| 1633 | + Returns: |
| 1634 | + dolfinx.fem.bcs.DirichletBC: A representation of |
| 1635 | + the boundary condition for modifying linear systems. |
| 1636 | + """ |
| 1637 | + # create value_fenics |
| 1638 | + if not self.multispecies: |
| 1639 | + function_space_value = bc.species.sub_function_space |
| 1640 | + else: |
| 1641 | + function_space_value = bc.species.collapsed_function_space |
| 1642 | + |
| 1643 | + # create K_S function |
| 1644 | + Q0 = fem.functionspace(self.mesh.mesh, ("DG", 0)) |
| 1645 | + K_S0 = fem.Function(Q0) |
| 1646 | + E_KS = fem.Function(Q0) |
| 1647 | + for subdomain in self.volume_subdomains: |
| 1648 | + entities = subdomain.locate_subdomain_entities_correct(self.volume_meshtags) |
| 1649 | + K_S0.x.array[entities] = subdomain.material.get_K_S_0(bc.species) |
| 1650 | + E_KS.x.array[entities] = subdomain.material.get_E_K_S(bc.species) |
| 1651 | + |
| 1652 | + K_S = K_S0 * ufl.exp(-E_KS / (festim.k_B * self.temperature_fenics)) |
| 1653 | + |
| 1654 | + bc.create_value( |
| 1655 | + temperature=self.temperature_fenics, |
| 1656 | + function_space=function_space_value, |
| 1657 | + t=self.t, |
| 1658 | + K_S=K_S, |
| 1659 | + ) |
| 1660 | + |
| 1661 | + # get dofs |
| 1662 | + if self.multispecies and isinstance(bc.value_fenics, (fem.Function)): |
| 1663 | + function_space_dofs = ( |
| 1664 | + bc.species.sub_function_space, |
| 1665 | + bc.species.collapsed_function_space, |
| 1666 | + ) |
| 1667 | + else: |
| 1668 | + function_space_dofs = bc.species.sub_function_space |
| 1669 | + |
| 1670 | + bc_dofs = bc.define_surface_subdomain_dofs( |
| 1671 | + facet_meshtags=self.facet_meshtags, |
| 1672 | + function_space=function_space_dofs, |
| 1673 | + ) |
| 1674 | + |
| 1675 | + # create form |
| 1676 | + if not self.multispecies and isinstance(bc.value_fenics, (fem.Function)): |
| 1677 | + # no need to pass the functionspace since value_fenics is already a Function |
| 1678 | + function_space_form = None |
| 1679 | + else: |
| 1680 | + function_space_form = bc.species.sub_function_space |
| 1681 | + |
| 1682 | + form = fem.dirichletbc( |
| 1683 | + value=bc.value_fenics, |
| 1684 | + dofs=bc_dofs, |
| 1685 | + V=function_space_form, |
| 1686 | + ) |
| 1687 | + |
| 1688 | + return form |
| 1689 | + |
| 1690 | + def update_time_dependent_values(self): |
| 1691 | + super().update_time_dependent_values() |
| 1692 | + |
| 1693 | + if self.temperature_time_dependent: |
| 1694 | + for bc in self.boundary_conditions: |
| 1695 | + if isinstance(bc, boundary_conditions.FixedConcentrationBC): |
| 1696 | + bc.update(self.t) |
0 commit comments