Skip to content

replace analytical integration with numerical - #110

Open
dschick wants to merge 18 commits into
developfrom
quadintegration
Open

replace analytical integration with numerical#110
dschick wants to merge 18 commits into
developfrom
quadintegration

Conversation

@dschick

@dschick dschick commented Jun 10, 2022

Copy link
Copy Markdown
Owner

following #109 the analytical integration can be replaced by numerical integration using scipy.integrate.quad.
This avoids doing the symbolig integration of the heat capacities and lin_therm_exp coefficients

@dschick

dschick commented Jun 10, 2022

Copy link
Copy Markdown
Owner Author

@mamattern, could you please check this branch, if it still works without analytical integration?

@dschick

dschick commented Jun 14, 2022

Copy link
Copy Markdown
Owner Author

Here are some comparisons for different types of temperature dependence of the heat capacity using scipy.integrate.quad's default setting for the numerical integration.
The integrator keeps the rel. error well below the default value of 1.49e-8.
This should be fine for most of the use cases.
The rel. error of the integrator is compared to the rel. difference between the analytical and numerical integrations

image
image
image
image

@dschick

dschick commented Jun 14, 2022

Copy link
Copy Markdown
Owner Author

Here I plot the same functions sampled in dT = 20K steps using np.interp

image
image
image
image

@dschick

dschick commented Jun 14, 2022

Copy link
Copy Markdown
Owner Author

Following this thread quad encounters some problems if the function has any edges, which can be the result of a linear interpolation, e.g. from np.interp.
The proposed solution in the thread renders the integration rather slow.

Using scipy.interpolation.interp1d with quadratic interpolation results in a much better results, although the input data again sampled with dT=20K
image

In general the interpolation of experimental data should be avoided and a fit function should be used instead.
Since one would not need to analytically integrate the fit function of the heat_capacity and lin_therm_exp these could be more complex functions.

@dschick

dschick commented Jun 15, 2022

Copy link
Copy Markdown
Owner Author

instead of using np.interp it is possible to use step-wise defined functions such as

def my_heat_capacity(T):
    T = np.array(T)
    res = np.ones_like(T, dtype=float)
    select = T>=634
    res[select] = -0.775/0.0718*np.abs((T[select]-634)/634)**0.0718 + 13.5+3.03*(T[select]-634)/634
    select = T<634
    res[select] =-2.46/0.0718*np.abs((T[select]-634)/634)**0.0718+37+3.03*(T[select]-634)/634
    return res

which results in a reasonable numerical integration
image

@mamattern

Copy link
Copy Markdown

Using the numerical integration branch and the above defined heat capacity by:

def my_heat_capacity(T):
    T = np.array(T)
    res = np.ones_like(T, dtype=float)
    select = T >= 634
    res[select] = (-0.775/0.0718*np.abs((T[select]-634)/634)**0.0718 + 13.5+3.03*(T[select]-634)/634)/58.69e-3
    select = T < 634
    res[select] = (-2.46/0.0718*np.abs((T[select]-634)/634)**0.0718+37+3.03*(T[select]-634)/634)/58.69e-3
    return res

prop_unit_cell['heat_capacity'] = ['lambda T: my_heat_capacity(T)', 400]

does not work and produces the following error:

  File "udkm1dsim_ni20nm_2tm_new.py", line 207, in <module>
    temp_map, delta_temp_map = h.get_temp_map(delays, Init_temp)

  File "ib\site-packages\udkm1Dsim\simulations\heat.py", line 740, in get_temp_map
    self.calc_temp_map(delays, init_temp)

  File "lib\site-packages\udkm1Dsim\simulations\heat.py", line 871, in calc_temp_map
    temp, _ = self.get_temperature_after_delta_excitation(fluence,

  File "lib\site-packages\udkm1Dsim\simulations\heat.py", line 701, in get_temperature_after_delta_excitation
    final_temp[i, 0] = brentq(fun, init_temp[i, 0], 1e5)

  File "lib\site-packages\scipy\optimize\_zeros_py.py", line 783, in brentq
    r = _zeros._brentq(f, a, b, xtol, rtol, maxiter, args, full_output, disp)

  File "lib\site-packages\udkm1Dsim\simulations\heat.py", line 697, in fun
    return (masses[idx]*quad(heat_capacities[idx][0],

  File "lib\site-packages\scipy\integrate\_quadpack_py.py", line 351, in quad
    retval = _quad(func, a, b, args, full_output, epsabs, epsrel, limit,

  File "lib\site-packages\scipy\integrate\_quadpack_py.py", line 463, in _quad
    return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)

  File "<lambdifygenerated-25>", line 2, in _lambdifygenerated
    [T_0, T_1] = _Dummy_26

@dschick

dschick commented Jun 15, 2022

Copy link
Copy Markdown
Owner Author

the problem is raised by sympy's lambdify function which cannot handle the function my_heat_capacity
the quick workaround is to set the lambda function again directly via the private property:
Ni_layer._heat_capacity = [lambda T: my_heat_capacity(T)]
This should work, however, I noticed, that the calculation of the get_temperature_after_delta_excitation() took like 30sec for this complex heat_capacity. The same slow-down should also happen for the heat_diffusion in case the ode-solver is working with the heat_capacity

@dschick

dschick commented Jun 15, 2022

Copy link
Copy Markdown
Owner Author

removing the vectorization of the heat_capacity function brings a huge speed improvement

def my_heat_capacity(t):
    if t >= 634:
        return -0.775/0.0718*np.abs((t-634)/634)**0.0718 + 13.5+3.03*(t-634)/634
    else:
        return -2.46/0.0718*np.abs((t-634)/634)**0.0718+37+3.03*(t-634)/634

@dschick

dschick commented Jun 15, 2022

Copy link
Copy Markdown
Owner Author

another option with vectorization would be using scipy.special.erf function:

Ni_layer.heat_capacity = '(0.5*erf(634-T)+1)*(-2.46/0.0718*abs((T-634)/634)**0.0718+37+3.03*(T-634)/634) + (0.5*erf(T-634)+1)*(-0.775/0.0718*abs((T-634)/634)**0.0718 + 13.5+3.03*(T-634)/634)'

this again works with the lambdification and seems to be reasonable fast.

@mamattern

mamattern commented Jun 15, 2022

Copy link
Copy Markdown

This does not work for me. I import from scipy.special import erf and use the line above. However, python return an error:

 File "lib\site-packages\scipy\integrate\_quadpack_py.py", line 463, in _quad
    return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit)

  File "<lambdifygenerated-1559>", line 2, in _lambdifygenerated
    return  (0.5*(erf(634-T)+1))*(-2.46/0.0718*abs((T-634)/634)**0.0718+37+3.03*(T-634)/634)/58.69e-3 + (0.5*(erf(T-634)+1))*(-0.775/0.0718*abs((T-634)/634)**0.0718 + 13.5+3.03*(T-634)/634)/58.69e-3

NameError: name 'erf' is not defined

If I use the private setting: Ni_layer._heat_capacity = [lamda T: ... erf(634-T) ..., 442.1] everything works fast and fine using the numeric integration branch.

@dschick

dschick commented Jun 16, 2022

Copy link
Copy Markdown
Owner Author

I just fixed the errors from the lambdification.
@mamattern could you please try again?
Could you compare the current version with the original np.interp version in terms of accuracy and speed?

Would be very interesting for the phonon calculation as well

@mamattern

Copy link
Copy Markdown

Now it works for me. I tested for numeric integration both possibilities:

Ni_layer.heat_capacity = '(0.5*erf(634-T)+1)*(-2.46/0.0718*abs((T-634)/634)**0.0718+37+3.03*(T-634)/634) + (0.5*erf(T-634)+1)*(-0.775/0.0718*abs((T-634)/634)**0.0718 + 13.5+3.03*(T-634)/634)'

and

Ni_layer._heat_capacity = [lamda T: ... erf(634-T) ..., 442.1]

the difference in the results is of the order e-10 and the calculation of the temperature after delta excitation took 7.4/7.2s, the calculation of the heat diffusion took 189/179s and the calculation of the strain map took 3.0/2.9s.

As a comparison I also tested the analytic integration possibility by giving the heat capacity and linear thermal expansion coefficient and their integrals by np.interp(). This took 0.02s, 129s and 0.4s. However, the difference in the order of 5-10% in the strain if the divergence of the quantities is relevant. For temperatures well below, the difference is of the order of e-4. In contrast, the temperature (both phonon and electron) shows a difference of e-4 in both cases, so is significantly less affected.

@dschick

dschick commented Jun 16, 2022

Copy link
Copy Markdown
Owner Author

okay, so without referring to the large difference in the strain_map (which I would still need to evaluate), I realize that the decrease in performance is not really balancing the increase in usability and code cleaning.

To that I end, I would stick to the analytical integration of the heat_capacity and lin_therm_exp for now. But I am thinking of maybe offering also the numerical integration as an option.
In addition, the option to manually set the analytical integral (e.g. by np.interp) should be better documented and maybe also offered in the according warning messages.

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.02%. Comparing base (e54254b) to head (28cc962).

Files with missing lines Patch % Lines
udkm1Dsim/simulations/heat.py 75.00% 1 Missing ⚠️
udkm1Dsim/simulations/phonons.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #110      +/-   ##
===========================================
- Coverage    86.13%   86.02%   -0.11%     
===========================================
  Files           10       10              
  Lines         3216     3170      -46     
===========================================
- Hits          2770     2727      -43     
+ Misses         446      443       -3     
Files with missing lines Coverage Δ
udkm1Dsim/structures/layers.py 87.34% <100.00%> (-0.68%) ⬇️
udkm1Dsim/simulations/heat.py 79.92% <75.00%> (ø)
udkm1Dsim/simulations/phonons.py 86.59% <66.66%> (+0.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq

codspeed-hq Bot commented Jul 20, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 84.38%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

❌ 1 regressed benchmark
✅ 12 untouched benchmarks
🆕 1 new benchmark
⏩ 6 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_calc_energy_map 95.6 ms 612.1 ms -84.38%
🆕 test_calc_sticks_from_temp_map N/A 5 s N/A

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing quadintegration (28cc962) with develop (e54254b)

Open in CodSpeed

Footnotes

  1. 6 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@dschick

dschick commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

I did some performance testing using numerical vs analytical integration for the heat_capacity and lin_therm_exp summarized in the table below:

heat_capacity:

  1. Heat.get_temperature_after_delta_excitation() 4.7ms --> 4.6ms
  2. Heat.calc_energy_map() 8.4ms --> 54.8ms

lin_therm_exp:
3. Phonon.calc_sticks_from_temp_map() 38.3ms --> 460.1ms

So for 1. it actually does not matter much, at least for constant heat capacities.
For 2. the increase is significant, but I guess bearable as one is not doing it too often
In case of 3. the change is way too large.

So in my view there are two option:

A. just keep it as it is and integrate functions by hand if necessary
B. only drop integration for heat_capacity and keep it for lin_therm_exp which is probably much less used as temperature-dependent quantity and if functions might be more monotonic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants