Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into temperature-as-float
Browse files Browse the repository at this point in the history
  • Loading branch information
RemDelaporteMathurin committed Jun 4, 2024
2 parents 2f3c60d + fb2c99f commit 6cb7559
Show file tree
Hide file tree
Showing 15 changed files with 139 additions and 20 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,14 @@ jobs:
uses: conda-incubator/setup-miniconda@v2
with:
activate-environment: myenv
mamba-version: "*"
# mamba-version: "*"
channels: conda-forge, defaults

- name: Create Conda environment
shell: bash -l {0}
run: |
# conda upgrade --strict-channel-priority -c conda-forge --all
conda install -c conda-forge 'conda-libmamba-solver<23.9'
mamba install -c conda-forge fenics
conda install -c conda-forge fenics numpy=1.24 python=3.10
- name: Install dependencies
shell: bash -l {0}
Expand Down
18 changes: 18 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@
"use_edit_page_button": True,
"repository_branch": "main",
"path_to_docs": "./docs/source",
"icon_links": [
{
"name": "PyPI",
"url": "https://pypi.org/project/FESTIM/",
"icon": "https://img.shields.io/pypi/dw/festim",
"type": "url",
},
{
"name": "Support Forum",
"url": "https://festim.discourse.group/",
"icon": "fa-brands fa-discourse",
},
{
"name": "Slack",
"url": "https://join.slack.com/t/festim-dev/shared_invite/zt-246hw8d6o-htWASLsbdosUo_2nRKCf9g",
"icon": "fa-brands fa-slack",
},
],
}

html_title = "FESTIM Documentation"
2 changes: 1 addition & 1 deletion docs/source/userguide/export_post_processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ The data can be accessed in three different ways:
print(flux_surf_3.t)
print(flux_surf_3.data)
print(my_derived_quantities.derived_quantities[2].data)
print(my_derived_quantities[2].data)
- export and read from a .csv file:

Expand Down
11 changes: 11 additions & 0 deletions docs/source/userguide/temperature.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ For a steady-state problem:
:ref:`Boundary conditions<boundary conditions>` and :ref:`heat sources<sources>` can then be applied to this heat transfer problem.

For transient problems, an initial condition is required:

.. code-block:: python
model.T = HeatTransferProblem(
transient=True,
initial_condition=300,
)
Initial conditions can be given as float, sympy expressions or a :class:`festim.InitialCondition` instance in order to read from a XDMF file (see :ref:`Initial Conditions<Initial Conditions>` for more details).

----------------
From a XDMF file
----------------
Expand Down
6 changes: 5 additions & 1 deletion festim/generic_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ def initialise(self):
# check that dt attribute is None if the sim is steady state
if not self.settings.transient and self.dt is not None:
raise AttributeError("dt must be None in steady state simulations")
if self.settings.transient and self.settings.final_time is None:
raise AttributeError(
"final_time argument must be provided to settings in transient simulations"
)
if self.settings.transient and self.dt is None:
raise AttributeError("dt must be provided in transient simulations")
if not self.T:
Expand Down Expand Up @@ -340,7 +344,7 @@ def initialise(self):

for export in self.exports:
if isinstance(export, festim.DerivedQuantities):
for q in export.derived_quantities:
for q in export:
if not isinstance(q, tuple(allowed_quantities[self.mesh.type])):
warnings.warn(
f"{type(q)} may not work as intended for {self.mesh.type} meshes"
Expand Down
12 changes: 7 additions & 5 deletions festim/temperature/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@

class Temperature:
"""
Description of Temperature
Class for Temperature in FESTIM
Args:
value (sp.Add, int, float, optional): The value of the temperature.
Only needed if type is not "expression". Defaults to None.
initial_value (sp.Add, int, float, optional): The initial value.
Only needed if type is not "expression". Defaults to None.
Defaults to None.
Attributes:
T (fenics.Function): the function attributed with temperature
T_n (fenics.Function): the previous function
value (sp.Add, int, float): the expression of temperature
expression (fenics.Expression): the expression of temperature as a
fenics object
Usage:
>>> import festim as F
>>> my_model = F.Simulation(...)
>>> my_model.T = F.Temperature(300 + 10 * F.x + F.t)
"""

def __init__(self, value=None) -> None:
# self.type = type
self.T = None
self.T_n = None
self.value = value
Expand Down
18 changes: 16 additions & 2 deletions festim/temperature/temperature_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class HeatTransferProblem(festim.Temperature):
Args:
transient (bool, optional): If True, a transient simulation will
be run. Defaults to True.
initial_condition (festim.InitialCondition, optional): The initial condition.
initial_condition (int, float, sp.Expr, festim.InitialCondition, optional): The initial condition.
Only needed if transient is True.
absolute_tolerance (float, optional): the absolute tolerance of the newton
solver. Defaults to 1e-03
Expand Down Expand Up @@ -56,6 +56,17 @@ def __init__(
self.boundary_conditions = []
self.sub_expressions = []

@property
def initial_condition(self):
return self._initial_condition

@initial_condition.setter
def initial_condition(self, value):
if isinstance(value, (int, float, sp.Expr)):
self._initial_condition = festim.InitialCondition(field="T", value=value)
else:
self._initial_condition = value

# TODO rename initialise?
def create_functions(self, materials, mesh, dt=None):
"""Creates functions self.T, self.T_n and test function self.v_T.
Expand All @@ -73,7 +84,10 @@ def create_functions(self, materials, mesh, dt=None):
self.T = f.Function(V, name="T")
self.T_n = f.Function(V, name="T_n")
self.v_T = f.TestFunction(V)

if self.transient and self.initial_condition is None:
raise AttributeError(
"Initial condition is required for transient heat transfer simulations"
)
if self.transient and self.initial_condition:
if isinstance(self.initial_condition.value, str):
if self.initial_condition.value.endswith(".xdmf"):
Expand Down
26 changes: 26 additions & 0 deletions test/simulation/test_initialise.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,29 @@ def test_wrong_type_temperature(value):
match="accepted types for T attribute are int, float, sympy.Expr or festim.Temperature",
):
my_model.T = value


def test_error_raised_when_no_final_time():
"""
Creates a Simulation object and checks that a AttributeError is raised
when .initialise() is called without a final_time argument
"""

my_model = F.Simulation()

my_model.mesh = F.MeshFromVertices([0, 1, 2, 3])

my_model.materials = F.Material(D_0=1, E_D=0, id=1)

my_model.T = F.Temperature(500)

my_model.settings = F.Settings(
absolute_tolerance=1e-10,
relative_tolerance=1e-10,
transient=True,
)

my_model.dt = F.Stepsize(1)

with pytest.raises(AttributeError, match="final_time argument must be provided"):
my_model.initialise()
2 changes: 2 additions & 0 deletions test/simulation/test_postprocessing_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def test_fluxes(self, my_sim):
assert np.isclose(data[1][3], -1 * grad_T * lambda_x_0)
assert np.isclose(data[1][4], -1 * grad_T * lambda_x_1 * -1)

@pytest.mark.filterwarnings("ignore:in 1D")
def test_performance_xdmf_export_every_N_iterations(self, my_sim, tmpdir):
"""Runs run_post_processing several times with different export.mode
values and checks that the xdmf
Expand Down Expand Up @@ -215,6 +216,7 @@ def test_performance_xdmf_export_every_N_iterations(self, my_sim, tmpdir):
for t_expected, t in zip(expected_times, times):
assert t_expected == pytest.approx(float(t))

@pytest.mark.filterwarnings("ignore:in 1D")
def test_xdmf_export_only_last_timestep(self, my_sim, tmpdir):
"""Runs run_post_processing with mode="last":
- when the time is not the final time and checks that nothing has been
Expand Down
21 changes: 21 additions & 0 deletions test/system/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def test_error_transient_without_stepsize():
transient=True,
absolute_tolerance=1e-10,
relative_tolerance=1e-10,
final_time=10,
)

my_model.dt = None
Expand Down Expand Up @@ -330,3 +331,23 @@ def test_materials_setter():
test_materials = F.Materials([])
my_model.materials = test_materials
assert my_model.materials is test_materials


def test_error_raised_when_no_IC_heat_transfer():
"""
Checks that an error is raised when no initial condition is provided for
transient heat transfer simulations
"""
my_model = F.Simulation()

my_model.T = F.HeatTransferProblem(transient=True, initial_condition=None)
my_model.mesh = F.MeshFromVertices(np.linspace(0, 1, 10))
my_model.materials = F.Material(1, 1, 0.1)
my_model.settings = F.Settings(1e-10, 1e-10, final_time=1e-7)
my_model.dt = F.Stepsize(1e-9)

with pytest.raises(
AttributeError,
match="Initial condition is required for transient heat transfer simulations",
):
my_model.initialise()
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from nis import match
import festim as F
import pytest

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from nis import match
import festim as F
import pytest

Expand Down
2 changes: 2 additions & 0 deletions test/unit/test_exports/test_trap_density_xdmf_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sympy as sp
from pathlib import Path
import numpy as np
import pytest


def test_trap_density_xdmf_export_intergration_with_simultion(tmpdir):
Expand Down Expand Up @@ -96,6 +97,7 @@ def test_trap_density_xdmf_export_write(tmpdir):
assert error_L2 < 1e-10


@pytest.mark.filterwarnings("ignore:in 1D")
def test_trap_density_xdmf_export_traps_materials_mixed(tmpdir):
"""Integration test for TrapDensityXDMF.write() with a trap material given
as a string. Creates a festim simulation and exports the trap density as an
Expand Down
6 changes: 3 additions & 3 deletions test/unit/test_exports/test_xdmf_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def folder(tmpdir):


def test_define_file_upon_construction(folder):
mesh = f.UnitIntervalMesh(10)
mesh = f.UnitSquareMesh(8, 8)
filename = "{}/my_filename.xdmf".format(folder)
my_xdmf = XDMFExport("solute", label="solute_label", filename=filename)

Expand All @@ -30,7 +30,7 @@ def test_default_filename():


class TestDefineFile:
mesh = f.UnitIntervalMesh(10)
mesh = f.UnitSquareMesh(8, 8)
my_xdmf = XDMFExport("solute", "my_label", filename="my_filename.xdmf")

def test_file_exists(self, folder):
Expand All @@ -44,7 +44,7 @@ def test_file_exists(self, folder):


class TestWrite:
mesh = f.UnitIntervalMesh(10)
mesh = f.UnitSquareMesh(8, 8)
V = f.FunctionSpace(mesh, "P", 1)
u = f.Function(V)
label_to_function = {"solute": u}
Expand Down
28 changes: 25 additions & 3 deletions test/unit/test_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ def thermal_cond(a):
bc1 = festim.DirichletBC(surfaces=[1], value=u, field="T")
bc2 = festim.FluxBC(surfaces=[2], value=2, field="T")

my_temp = festim.HeatTransferProblem(
transient=True, initial_condition=festim.InitialCondition(field="T", value=0)
)
my_temp = festim.HeatTransferProblem(transient=True, initial_condition=0)
my_temp.boundary_conditions = [bc1, bc2]
my_temp.sources = [festim.Source(-4, volume=[1], field="T")]
my_temp.create_functions(my_mats, my_mesh, dt=dt)
Expand Down Expand Up @@ -133,6 +131,30 @@ class is created from this file and the error norm between the written and
assert error_L2 < 1e-9


def test_temperature_with_expr():
"""
Test for the InitialCondition.create_functions() with a sympy expression
and checks that the error norm between the expected and computed
functions is below a certain threshold.
"""
# create function to be comapared
mesh = fenics.UnitSquareMesh(10, 10)
# TempFromXDMF needs a festim mesh
my_mesh = festim.Mesh()
my_mesh.dx = fenics.dx
my_mesh.ds = fenics.ds
my_mesh.mesh = mesh
my_mats = festim.Materials(
[festim.Material(id=1, D_0=1, E_D=0, thermal_cond=1, heat_capacity=1, rho=1)]
)
my_T = festim.HeatTransferProblem(transient=True, initial_condition=300 + festim.x)
my_T.create_functions(mesh=my_mesh, materials=my_mats, dt=festim.Stepsize(2))

expected_expr = fenics.Expression("300 + x[0]", degree=2)
error_L2 = fenics.errornorm(expected_expr, my_T.T_n, "L2")
assert error_L2 < 1e-9


def test_temperature_from_xdmf_create_functions(tmpdir):
"""Test for the TemperatureFromXDMF.create_functions().
Creates a function, writes it to an XDMF file, then a TemperatureFromXDMF
Expand Down

0 comments on commit 6cb7559

Please sign in to comment.