4040from cmdstanpy .cmdstan_args import (
4141 CmdStanArgs ,
4242 GenerateQuantitiesArgs ,
43+ LaplaceArgs ,
44+ Method ,
4345 OptimizeArgs ,
4446 SamplerArgs ,
4547 VariationalArgs ,
4648)
4749from cmdstanpy .compiler_opts import CompilerOptions
4850from cmdstanpy .stanfit import (
4951 CmdStanGQ ,
52+ CmdStanLaplace ,
5053 CmdStanMCMC ,
5154 CmdStanMLE ,
5255 CmdStanVB ,
@@ -393,7 +396,7 @@ def format(
393396 + '.bak-'
394397 + datetime .now ().strftime ("%Y%m%d%H%M%S" ),
395398 )
396- with ( open (self .stan_file , 'w' ) ) as file_handle :
399+ with open (self .stan_file , 'w' ) as file_handle :
397400 file_handle .write (result )
398401 else :
399402 print (result )
@@ -589,6 +592,8 @@ def optimize(
589592 refresh : Optional [int ] = None ,
590593 time_fmt : str = "%Y%m%d%H%M%S" ,
591594 timeout : Optional [float ] = None ,
595+ jacobian : bool = False ,
596+ # would be nice to move this further up, but that's a breaking change
592597 ) -> CmdStanMLE :
593598 """
594599 Run the specified CmdStan optimize algorithm to produce a
@@ -690,6 +695,11 @@ def optimize(
690695
691696 :param timeout: Duration at which optimization times out in seconds.
692697
698+ :param jacobian: Whether or not to use the Jacobian adjustment for
699+ constrained variables in optimization. By default this is false,
700+ meaning optimization yields the Maximum Likehood Estimate (MLE).
701+ Setting it to true yields the Maximum A Posteriori Estimate (MAP).
702+
693703 :return: CmdStanMLE object
694704 """
695705 optimize_args = OptimizeArgs (
@@ -703,8 +713,15 @@ def optimize(
703713 history_size = history_size ,
704714 iter = iter ,
705715 save_iterations = save_iterations ,
716+ jacobian = jacobian ,
706717 )
707718
719+ if jacobian and cmdstan_version_before (2 , 32 , self .exe_info ()):
720+ raise ValueError (
721+ "Jacobian adjustment for optimization is only supported "
722+ "in CmdStan 2.32 and above."
723+ )
724+
708725 with MaybeDictToFilePath (data , inits ) as (_data , _inits ):
709726 args = CmdStanArgs (
710727 self ._name ,
@@ -1606,6 +1623,8 @@ def log_prob(
16061623 self ,
16071624 params : Union [Dict [str , Any ], str , os .PathLike ],
16081625 data : Union [Mapping [str , Any ], str , os .PathLike , None ] = None ,
1626+ * ,
1627+ jacobian : bool = True ,
16091628 ) -> pd .DataFrame :
16101629 """
16111630 Calculate the log probability and gradient at the given parameter
@@ -1626,6 +1645,9 @@ def log_prob(
16261645 either as a dictionary with entries matching the data variables,
16271646 or as the path of a data file in JSON or Rdump format.
16281647
1648+ :param jacobian: Whether or not to enable the Jacobian adjustment
1649+ for constrained parameters. Defaults to ``True``.
1650+
16291651 :return: A pandas.DataFrame containing columns "lp__" and additional
16301652 columns for the gradient values. These gradients will be for the
16311653 unconstrained parameters of the model.
@@ -1641,6 +1663,7 @@ def log_prob(
16411663 str (self .exe_file ),
16421664 "log_prob" ,
16431665 f"constrained_params={ _params } " ,
1666+ f"jacobian={ int (jacobian )} " ,
16441667 ]
16451668 if _data is not None :
16461669 cmd += ["data" , f"file={ _data } " ]
@@ -1669,6 +1692,104 @@ def log_prob(
16691692 result = pd .read_csv (output , comment = "#" )
16701693 return result
16711694
1695+ def laplace_sample (
1696+ self ,
1697+ data : Union [Mapping [str , Any ], str , os .PathLike , None ] = None ,
1698+ mode : Union [CmdStanMLE , str , os .PathLike , None ] = None ,
1699+ draws : Optional [int ] = None ,
1700+ * ,
1701+ jacobian : bool = True , # NB: Different than optimize!
1702+ seed : Optional [int ] = None ,
1703+ output_dir : OptionalPath = None ,
1704+ sig_figs : Optional [int ] = None ,
1705+ save_profile : bool = False ,
1706+ show_console : bool = False ,
1707+ refresh : Optional [int ] = None ,
1708+ time_fmt : str = "%Y%m%d%H%M%S" ,
1709+ timeout : Optional [float ] = None ,
1710+ opt_args : Optional [Dict [str , Any ]] = None ,
1711+ ) -> CmdStanLaplace :
1712+ if cmdstan_version_before (2 , 32 , self .exe_info ()):
1713+ raise ValueError (
1714+ "Method 'laplace_sample' not available for CmdStan versions "
1715+ "before 2.32"
1716+ )
1717+ if opt_args is not None and mode is not None :
1718+ raise ValueError (
1719+ "Cannot specify both 'opt_args' and 'mode' arguments"
1720+ )
1721+ if mode is None :
1722+ optimize_args = {
1723+ "seed" : seed ,
1724+ "sig_figs" : sig_figs ,
1725+ "jacobian" : jacobian ,
1726+ "save_profile" : save_profile ,
1727+ "show_console" : show_console ,
1728+ "refresh" : refresh ,
1729+ "time_fmt" : time_fmt ,
1730+ "timeout" : timeout ,
1731+ "output_dir" : output_dir ,
1732+ }
1733+ optimize_args .update (opt_args or {})
1734+ optimize_args ['time_fmt' ] = 'opt-' + time_fmt
1735+ try :
1736+ cmdstan_mode : CmdStanMLE = self .optimize (
1737+ data = data ,
1738+ ** optimize_args , # type: ignore
1739+ )
1740+ except Exception as e :
1741+ raise RuntimeError (
1742+ "Failed to run optimizer on model. "
1743+ "Consider supplying a mode or additional optimizer args"
1744+ ) from e
1745+ elif not isinstance (mode , CmdStanMLE ):
1746+ cmdstan_mode = from_csv (mode ) # type: ignore # we check below
1747+ else :
1748+ cmdstan_mode = mode
1749+
1750+ if cmdstan_mode .runset .method != Method .OPTIMIZE :
1751+ raise ValueError (
1752+ "Mode must be a CmdStanMLE or a path to an optimize CSV"
1753+ )
1754+
1755+ mode_jacobian = (
1756+ cmdstan_mode .runset ._args .method_args .jacobian # type: ignore
1757+ )
1758+ if mode_jacobian != jacobian :
1759+ raise ValueError (
1760+ "Jacobian argument to optimize and laplace must match!\n "
1761+ f"Laplace was run with jacobian={ jacobian } ,\n "
1762+ f"but optimize was run with jacobian={ mode_jacobian } "
1763+ )
1764+
1765+ laplace_args = LaplaceArgs (
1766+ cmdstan_mode .runset .csv_files [0 ], draws , jacobian
1767+ )
1768+
1769+ with MaybeDictToFilePath (data ) as (_data ,):
1770+ args = CmdStanArgs (
1771+ self ._name ,
1772+ self ._exe_file ,
1773+ chain_ids = None ,
1774+ data = _data ,
1775+ seed = seed ,
1776+ output_dir = output_dir ,
1777+ sig_figs = sig_figs ,
1778+ save_profile = save_profile ,
1779+ method_args = laplace_args ,
1780+ refresh = refresh ,
1781+ )
1782+ dummy_chain_id = 0
1783+ runset = RunSet (args = args , chains = 1 , time_fmt = time_fmt )
1784+ self ._run_cmdstan (
1785+ runset ,
1786+ dummy_chain_id ,
1787+ show_console = show_console ,
1788+ timeout = timeout ,
1789+ )
1790+ runset .raise_for_timeouts ()
1791+ return CmdStanLaplace (runset , cmdstan_mode )
1792+
16721793 def _run_cmdstan (
16731794 self ,
16741795 runset : RunSet ,
0 commit comments