import copy
import warnings
import functools
from .utilitiesmethods.metropolis_hastings import sampler as sample_with_metropolis_hastings
from .utilitiesmethods.LMC import sampler as sample_with_LMC
from .utilitiesmethods.PTLMC import sampler as sample_with_PTLMC
[docs]
def create_sampler(sampler, options):
"""
Construct a sampler function for direct use by |surmise| calibrators. The
following example demonstrates its use.
.. code-block:: python
sample_with_PTLMC = surmise.create_sampler("PTLMC", ptlmc_args)
results = sample_with_PTLMC(
logpost_func=log_posterior,
draw_func=draw_from_start_distribution,
scipy_stats_rng=np.random.default_rng(RAND_SEED)
)
For typical use cases, samplers are created automatically under-the-hood on
behalf of users. Therefore, there is generally no need to explicitly create
or access samplers. This function is in the |surmise| public interface only
as an advanced feature for use by developers and power users.
.. todo::
* The current implementation prevents users from providing a custom
sampler to calibrators. Consider allowing ``sampler`` to be a
user-provided sampler function that we assume has the necessary
interface. This function could then confirm that options is ``None``
or an empty ``dict`` and just pass that function along. If the
calibrator interface is updated so that calling code must provide a
sampler identifier, then that argument could also be setup in this
same way.
Parameters
----------
sampler :
Name of desired sampler offered by |surmise|
options :
``dict`` of sampler-specific arguments that fully characterize the
desired sampler. Refer to the documentation of each sampler for more
information.
Returns
-------
:
The desired sampler function.
"""
KEY = "expertMode"
if sampler.lower() == "metropolis_hastings":
return functools.partial(sample_with_metropolis_hastings, **options)
elif sampler.upper() == "LMC":
lmc_options = copy.deepcopy(options)
if KEY in lmc_options:
if not isinstance(lmc_options[KEY], bool):
raise ValueError(f"{KEY} value must be a boolean")
elif not lmc_options[KEY]:
msg = "{} is included for unofficial research purposes only"
raise ValueError(msg.format(sampler))
del lmc_options[KEY]
else:
msg = "{} is included for unofficial research purposes only"
raise ValueError(msg.format(sampler))
# Emit warning to extend a helping hand to the experts.
msg = f"Using unofficial research {sampler} sampler"
warnings.warn(msg)
return functools.partial(sample_with_LMC, **lmc_options)
elif sampler.upper() == "PTLMC":
return functools.partial(sample_with_PTLMC, **options)
raise TypeError(f"Invalid sampler ({sampler})")