JohannesBuchner / UltraNest

Fit and compare complex models reliably and rapidly. Advanced nested sampling.
https://johannesbuchner.github.io/UltraNest/
Other
142 stars 30 forks source link

No attribute '__name__' error #54

Closed yqiuu closed 2 years ago

yqiuu commented 2 years ago

Description

The sampler raises an error when I use an object created by partial or a class with __call__. This is not very convenient.

What I Did

For example,

from functools import partial

def test_func(x, mu):
    return -np.sum((x - mu)**2, axis=-1)

sampler = ultranest.ReactiveNestedSampler(['x0', 'x1'], Test())
sampler.run()

yields

~/programs/anaconda3/lib/python3.7/site-packages/ultranest/utils.py in vectorize(function)
    131         return np.asarray([function(arg) for arg in args])
    132 
--> 133     vectorized.__name__ = function.__name__
    134     return vectorized
    135 

AttributeError: 'functools.partial' object has no attribute '__name__'

Similarly,

class Test:
    def __call__(self, x):
        return -np.sum(x**2, axis=-1)

sampler = ultranest.ReactiveNestedSampler(['x0', 'x1'], Test())
sampler.run()

yields

~/programs/anaconda3/lib/python3.7/site-packages/ultranest/utils.py in vectorize(function)
    131         return np.asarray([function(arg) for arg in args])
    132 
--> 133     vectorized.__name__ = function.__name__
    134     return vectorized
    135 

AttributeError: 'Test' object has no attribute '__name__'
JohannesBuchner commented 2 years ago

probably could be replaced by getattr(function, '__name__')

The axis argument in your functions seems like your function is already vectorized. So perhaps you want to call ReactiveNestedSampler with vectorized=True, in which case this problem does not occur.

yqiuu commented 2 years ago

Using getattr(function, '__name__') also leads to AttributeError. This issue is not related to vectorized=True, since you can have a function obtained by partial which is not vectorized. In fact, I have found that the issue can be resolved by the following way:

import ultranest
from functools import partial

def test_func(x, mu):
    return -np.sum((x - mu)**2, axis=-1)

test_partial = partial(test_func, mu=0.5)
test_partial.__name__ = "XX" # XX can be anything.

sampler = ultranest.ReactiveNestedSampler(['x0', 'x1'], test_partial)
sampler.run()

This is a bit wired to me. I am just wondering why the code uses loglike.__name__.

JohannesBuchner commented 2 years ago

If your function is not vectorized, ultranest creates (with the vectorize() function in utils.py) a vectorized version. For debugging neatness, it tries to give that wrapper the same names as the original function (as opposed to "vectorized" always). For example, when you do print(sampler.loglike, sampler.transform). If your function is already vectorized, and you use vectorized=True, vectorize() is never called.

Could you try if vectorized.__name__ = getattr(function, '__name__', vectorized.__name__) works?

JohannesBuchner commented 2 years ago

Ok, I think this is fixed in the latest commit https://app.circleci.com/pipelines/github/JohannesBuchner/UltraNest/195/workflows/c6100458-973a-4323-87c4-f7a5416890d0/jobs/192

Can you please check if it works for you?

yqiuu commented 2 years ago

Yes, it works now. Thanks.