Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance testing #183

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/elli/dispersions/base_dispersion.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy as np
import numpy.typing as npt
import pandas as pd
from lmfit import Parameter
from numpy.lib.scimath import sqrt

from .. import dispersions
Expand Down Expand Up @@ -56,6 +57,8 @@ def _fill_params_dict(template: dict, *args, **kwargs) -> dict:

for i, val in enumerate(args):
key = list(template.keys())[i]
if isinstance(val, Parameter):
val = val.value
params[key] = val
pos_arguments.add(key)

Expand All @@ -64,6 +67,8 @@ def _fill_params_dict(template: dict, *args, **kwargs) -> dict:
raise InvalidParameters(
f"Parameter {key} already set by positional argument"
)
if isinstance(value, Parameter):
value = value.value
params[key] = value

return params
Expand All @@ -80,6 +85,17 @@ def __init__(self, *args, **kwargs):
if self.single_params[param] is None:
raise InvalidParameters(f"Please specify parameter {param}")

self.last_lbda = None
self.hash_single_params = None
self.hash_rep_params = None

def _hash_params(self, params: Union[dict, List[dict]]) -> int:
"""Creates an single_params_dict or the repeating_params_list."""
if isinstance(params, list):
return hash(tuple([self._hash_params(dictionary) for dictionary in params]))
else:
return hash(tuple([item for _, item in params.items()]))

@abstractmethod
def dielectric_function(self, lbda: npt.ArrayLike) -> npt.NDArray:
"""Calculates the dielectric function in a given wavelength window.
Expand Down Expand Up @@ -114,6 +130,40 @@ def get_dielectric(self, lbda: Optional[npt.ArrayLike] = None) -> npt.NDArray:
"""Returns the dielectric constant for wavelength 'lbda' default unit (nm)
in the convention ε1 + iε2."""
lbda = self.default_lbda_range if lbda is None else lbda

from .table_epsilon import TableEpsilon
from .table_index import Table
from .pseudo_dielectric import PseudoDielectricFunction

if not isinstance(self, (DispersionSum, IndexDispersionSum)):
if isinstance(self, (TableEpsilon, Table, PseudoDielectricFunction)):
if self.last_lbda is lbda:
return self.cached_diel
else:
self.last_lbda = lbda
self.cached_diel = np.asarray(
self.dielectric_function(lbda), dtype=np.complex128
)
return self.cached_diel
else:
new_single_hash = self._hash_params(self.single_params)
new_rep_hash = self._hash_params(self.rep_params)

if (
self.last_lbda is lbda
and self.hash_single_params == new_single_hash
and self.hash_rep_params == new_rep_hash
):
return self.cached_diel
else:
self.last_lbda = lbda
self.hash_single_params = new_single_hash
self.hash_rep_params = new_rep_hash
self.cached_diel = np.asarray(
self.dielectric_function(lbda), dtype=np.complex128
)
return self.cached_diel

return np.asarray(self.dielectric_function(lbda), dtype=np.complex128)

def get_refractive_index(self, lbda: Optional[npt.ArrayLike] = None) -> npt.NDArray:
Expand Down
14 changes: 10 additions & 4 deletions src/elli/fitting/decorator_psi_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,9 @@ def fit_function(
"""
result = self.model(lbda, params)

resid_rhor = rhor - result.rho.real
resid_rhoi = rhoi - result.rho.imag
sim_rho = result.rho
resid_rhor = rhor - sim_rho.real
resid_rhoi = rhoi - sim_rho.imag

return np.concatenate((resid_rhor, resid_rhoi))

Expand All @@ -268,11 +269,10 @@ def fit(self, method="leastsq"):
Returns:
Result: The fitting result
"""
rho = calc_rho(self.exp_data)
res = minimize(
self.fit_function,
self.params,
args=(rho.index.to_numpy(), rho.values.real, rho.values.imag),
args=(self.lbda, self.rhor, self.rhoi),
method=method,
)

Expand Down Expand Up @@ -390,7 +390,13 @@ def __init__(
"""
super().__init__()
self.model = model

self.exp_data = exp_data
tmp_rho = calc_rho(self.exp_data)
self.lbda = tmp_rho.index.to_numpy()
self.rhor = tmp_rho.values.real
self.rhoi = tmp_rho.values.imag

self.params = params
self.fitted_params = params.copy()
self.angle = angle
Expand Down
7 changes: 5 additions & 2 deletions src/elli/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ class Solver(ABC):
def calculate(self) -> Result:
pass

def __init__(self, experiment: "Experiment") -> None:
self.experiment = deepcopy(experiment)
def __init__(self, experiment: "Experiment", save_experiment: bool = False) -> None:
if save_experiment:
self.experiment = deepcopy(experiment)
else:
self.experiment = experiment
self.structure = self.experiment.structure
self.lbda = self.experiment.lbda
self.theta_i = self.experiment.theta_i
Expand Down
7 changes: 5 additions & 2 deletions src/elli/solver4x4.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,12 @@ def get_k_z(
return sqrt(k_z2)

def __init__(
self, experiment: "Experiment", propagator: Propagator = PropagatorExpm()
self,
experiment: "Experiment",
propagator: Propagator = PropagatorExpm(),
save_experiment: bool = False,
) -> None:
super().__init__(experiment)
super().__init__(experiment, save_experiment)
self.propagator = propagator

def calculate(self) -> Result:
Expand Down
114 changes: 114 additions & 0 deletions tests/benchmark_fitting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
"""Benchmark for using the formula dispersion"""

import elli
import numpy as np
from elli.fitting import ParamsHist, fit
from fixtures import datadir
from pytest import fixture


def test_fitting_structure_creation(benchmark, datadir):
ANGLE = 70
rii_db = elli.db.RII()
Si = rii_db.get_mat("Si", "Aspnes")

psi_delta = (
elli.read_nexus_psi_delta(datadir / "SiO2onSi.ellips.nxs")
.loc[ANGLE]
.loc[210:800]
)

params = ParamsHist()
params.add("SiO2_n0", value=1.6, min=-100, max=100, vary=True)
params.add("SiO2_n1", value=36, min=-40000, max=40000, vary=False)
params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_k0", value=0, min=-100, max=100, vary=False)
params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_d", value=20, min=0, max=40000, vary=True)

@fit(psi_delta, params)
def model(lbda, params):
SiO2 = elli.Cauchy(
params["SiO2_n0"],
params["SiO2_n1"],
params["SiO2_n2"],
params["SiO2_k0"],
params["SiO2_k1"],
params["SiO2_k2"],
).get_mat()

return elli.Structure(
elli.AIR,
[elli.Layer(SiO2, params["SiO2_d"])],
Si,
).evaluate(lbda, ANGLE, solver=elli.Solver2x2)

result = benchmark.pedantic(
model.fit,
args=(),
iterations=1,
rounds=10,
)
assert result.chisqr < 0.02


def test_fitting_structure_updates(benchmark, datadir):
ANGLE = 70
rii_db = elli.db.RII()
Si = rii_db.get_mat("Si", "Aspnes")

psi_delta = (
elli.read_nexus_psi_delta(datadir / "SiO2onSi.ellips.nxs")
.loc[ANGLE]
.loc[210:800]
)

params = ParamsHist()
params.add("SiO2_n0", value=1.6, min=-100, max=100, vary=True)
params.add("SiO2_n1", value=36, min=-40000, max=40000, vary=False)
params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_k0", value=0, min=-100, max=100, vary=False)
params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False)
params.add("SiO2_d", value=20, min=0, max=40000, vary=True)

SiO2 = elli.Cauchy(
params["SiO2_n0"],
params["SiO2_n1"],
params["SiO2_n2"],
params["SiO2_k0"],
params["SiO2_k1"],
params["SiO2_k2"],
)

SiO2_mat = SiO2.get_mat()

layer = elli.Layer(SiO2_mat, params["SiO2_d"])

structure = elli.Structure(
elli.AIR,
[layer],
Si,
)

@fit(psi_delta, params)
def model(lbda, params):
SiO2.single_params["n0"] = params["SiO2_n0"].value
SiO2.single_params["n1"] = params["SiO2_n1"].value
SiO2.single_params["n2"] = params["SiO2_n2"].value
SiO2.single_params["k0"] = params["SiO2_k0"].value
SiO2.single_params["k1"] = params["SiO2_k1"].value
SiO2.single_params["k2"] = params["SiO2_k2"].value

layer.set_thickness(params["SiO2_d"])

return structure.evaluate(lbda, ANGLE, solver=elli.Solver2x2)

result = benchmark.pedantic(
model.fit,
args=(),
iterations=1,
rounds=10,
)
assert result.chisqr < 0.02
Binary file added tests/benchmark_fitting/SiO2onSi.ellips.nxs
Binary file not shown.
Loading