Skip to content

Commit e485810

Browse files
authored
Fix issue with frozen Pmodels not being preserved by dipolarmodel.py and dd_* and bg_* imports are now copies (#511)
* Fix issue with frozen Pmodels not being preserved by dipolarmodel.py * Added copy to tests * Automatically copy the `bg_model`and `dd_model` upon import It was reported in #499 that, there seemed to be a bug that when a Pmodel with a frozen parameter is passed to dipolarmodel it is forgotten. However, by fixing this issue we cause problems as the base dd_* (bg_*) models are globals so the freezing the PModel affects the global not the local instance. In my opinion this is bad implementation. To resolve this further problem I have modified how dd_* and bg_* models are imported so they now import as copies of the global. Resolving this issue, with no wider effects.
1 parent c37c2c0 commit e485810

7 files changed

Lines changed: 81 additions & 4 deletions

File tree

deerlab/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
# __init__.py
2-
from .dd_models import *
3-
from .bg_models import *
2+
from . import dd_models as _dd_models_mod
3+
from . import bg_models as _bg_models_mod
4+
5+
# Define __getattr__ early so submodules that do `from deerlab import bg_*`
6+
# during their own import (e.g. dipolarmodel) can resolve names via this hook.
7+
def __getattr__(name):
8+
if name in _dd_models_mod.__all__:
9+
return _dd_models_mod.__getattr__(name)
10+
if name in _bg_models_mod.__all__:
11+
return _bg_models_mod.__getattr__(name)
12+
raise AttributeError(f"module 'deerlab' has no attribute {name!r}")
13+
414
from .model import Model, Penalty, Parameter, link, lincombine, merge, relate
515
from .deerload import deerload
616
from .selregparam import selregparam

deerlab/bg_models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import math as m
88
from numpy import pi
99
import inspect
10+
from copy import deepcopy as _deepcopy
1011
from deerlab.dipolarkernel import dipolarkernel
1112
from deerlab.utils import formatted_table
1213
from deerlab.model import Model
@@ -513,3 +514,19 @@ def _poly3(t,p0,p1,p2,p3):
513514
bg_poly3.p3.set(description='3rd order weight', lb=-200, ub=200, par0=-1, unit=r'μs\ :sup:`-3`')
514515
# Add documentation
515516
bg_poly3.__doc__ = _docstring(bg_poly3,notes)
517+
518+
519+
# ---------------------------------------------------------------------------
520+
# Return a fresh deepcopy on every attribute access so that modifications
521+
# to a retrieved model never affect the global template.
522+
# ---------------------------------------------------------------------------
523+
_templates = {name: obj for name, obj in list(globals().items()) if name.startswith('bg_')}
524+
for _name in list(_templates):
525+
del globals()[_name]
526+
527+
__all__ = list(_templates.keys())
528+
529+
def __getattr__(name):
530+
if name in _templates:
531+
return _deepcopy(_templates[name])
532+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

deerlab/dd_models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import inspect
88
import numpy as np
99
import scipy.special as spc
10+
from copy import deepcopy as _deepcopy
1011
from deerlab.model import Model
1112
from deerlab.utils import formatted_table
1213

@@ -1042,3 +1043,19 @@ def _wormgauss(r,contour,persistence,std):
10421043
dd_wormgauss.std.set(description='Gaussian standard deviation', lb=0.01, ub=5, par0=0.2, unit='nm')
10431044
# Add documentation
10441045
dd_wormgauss.__doc__ = _dd_docstring(dd_wormgauss,notes) + docstr_example('dd_wormgauss')
1046+
1047+
1048+
# ---------------------------------------------------------------------------
1049+
# Return a fresh deepcopy on every attribute access so that modifications
1050+
# to a retrieved model never affect the global template.
1051+
# ---------------------------------------------------------------------------
1052+
_templates = {name: obj for name, obj in list(globals().items()) if name.startswith('dd_')}
1053+
for _name in list(_templates):
1054+
del globals()[_name]
1055+
1056+
__all__ = list(_templates.keys())
1057+
1058+
def __getattr__(name):
1059+
if name in _templates:
1060+
return _deepcopy(_templates[name])
1061+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

deerlab/dipolarmodel.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ def _importparameter(parameter):
157157
'par0' : parameter.par0,
158158
'description' : parameter.description,
159159
'unit' : parameter.unit,
160-
'linear' : parameter.linear
160+
'linear' : parameter.linear,
161+
'frozen' : parameter.frozen,
162+
'value' : parameter.value
161163
}
162164
#------------------------------------------------------------------------
163165

test/test_ddmodels.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,17 @@ def test_dd_wormchain():
104104
assert_ddmodel(dl.dd_wormchain)
105105

106106
def test_dd_wormgauss():
107-
assert_ddmodel(dl.dd_wormgauss)
107+
assert_ddmodel(dl.dd_wormgauss)
108+
109+
110+
def test_freezing_model():
111+
"Check that freezing parameters of a model works as expected"
112+
113+
# Create model and freeze parameters
114+
model = dl.dd_gauss.copy()
115+
model.mean.freeze(3)
116+
model.std.freeze(0.2)
117+
118+
# Check that the frozen parameters are correctly set
119+
assert model.mean.frozen and model.mean.value == 3
120+
assert model.std.frozen and model.std.value == 0.2

test/test_dipolarmodel.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,21 @@ def test_fit_3pathways(V3path):
241241
assert np.allclose(result.model,V3path)
242242
# ======================================================================
243243

244+
# ======================================================================
245+
def test_freeze_fit_linear(V1path):
246+
"Check that the model can be correctly fitted with a frozen linear parameter"
247+
248+
dd_model = dd_gauss
249+
dd_model.std.freeze(0.25)
250+
251+
assert dd_model.std.frozen and dd_model.std.value == 0.25
252+
Vmodel = dipolarmodel(t,r,dd_gauss,bg_hom3d,npathways=1)
253+
assert Vmodel.std.frozen and Vmodel.std.value == 0.25
254+
255+
result = fit(Vmodel,V1path,ftol=1e-4)
256+
257+
assert np.allclose(result.std,0.25)
258+
# ======================================================================
244259
# Fixtures
245260
# ----------------------------------------------------------------------
246261
@fixture(scope='module')

test/test_model_penalty.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def test_weight_freeze(penalty_fcn):
6666
penaltyobj = Penalty(penalty_fcn,'icc')
6767
penaltyobj.weight.freeze(0.5)
6868
assert penaltyobj.weight.frozen==True and penaltyobj.weight.value==0.5
69+
penaltyobj.weight.unfreeze()
6970
# ======================================================================
7071

7172
# ======================================================================
@@ -74,6 +75,7 @@ def test_fit(penalty_fcn, model, mock_data, selection):
7475
"Check fitting with a penalty with ICC-selected weight"
7576
penaltyobj = Penalty(penalty_fcn, selection)
7677
penaltyobj.weight.set(lb=1e-6,ub=1e1)
78+
assert not penaltyobj.weight.frozen
7779
result = fit(model,mock_data,x,penalties=penaltyobj)
7880
assert ovl(result.model,mock_data)>0.975
7981
# ======================================================================
@@ -89,6 +91,7 @@ def test_fit_with_penalty_weight(penalty_fcn, model, mock_data, case):
8991
penaltyobj.weight.freeze(0.00001)
9092
result = fit(model,mock_data,x,penalties=penaltyobj)
9193
assert ovl(result.model,mock_data)>0.975
94+
penaltyobj.weight.unfreeze()
9295
# ======================================================================
9396

9497
# ======================================================================

0 commit comments

Comments
 (0)