1 #ifndef STAN_MATH_REV_FUN_LUB_CONSTRAIN_HPP
2 #define STAN_MATH_REV_FUN_LUB_CONSTRAIN_HPP
3 
4 #include <stan/math/rev/core.hpp>
5 #include <stan/math/prim/meta.hpp>
6 #include <stan/math/rev/fun/lb_constrain.hpp>
7 #include <stan/math/prim/fun/lub_constrain.hpp>
8 #include <stan/math/rev/fun/ub_constrain.hpp>
9 #include <cmath>
10 
11 namespace stan {
12 namespace math {
13 
14 /**
15  * Return the lower and upper-bounded scalar derived by
16  * transforming the specified free scalar given the specified
17  * lower and upper bounds.
18  *
19  * <p>The transform is the transformed and scaled inverse logit,
20  *
21  * <p>\f$f(x) = L + (U - L) \mbox{logit}^{-1}(x)\f$
22  *
23  * @tparam T Scalar.
24  * @tparam L Scalar.
25  * @tparam U Scalar.
26  * @param[in] x Free scalar to transform.
27  * @param[in] lb Lower bound.
28  * @param[in] ub Upper bound.
29  * @return Lower- and upper-bounded scalar derived from transforming
30  *   the free scalar.
31  * @throw std::domain_error if ub <= lb
32  */
33 template <typename T, typename L, typename U,
34           require_all_stan_scalar_t<T, L, U>* = nullptr,
35           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub)36 inline auto lub_constrain(const T& x, const L& lb, const U& ub) {
37   using std::exp;
38   const auto lb_val = value_of(lb);
39   const auto ub_val = value_of(ub);
40   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
41   const bool is_ub_inf = ub_val == INFTY;
42   if (unlikely(is_ub_inf && is_lb_inf)) {
43     return identity_constrain(x, ub, lb);
44   } else if (unlikely(is_ub_inf)) {
45     return lb_constrain(identity_constrain(x, ub), lb);
46   } else if (unlikely(is_lb_inf)) {
47     return ub_constrain(identity_constrain(x, lb), ub);
48   } else {
49     check_less("lub_constrain", "lb", lb_val, ub_val);
50     auto diff = ub_val - lb_val;
51     double inv_logit_x = inv_logit(value_of(x));
52     return make_callback_var(
53         diff * inv_logit_x + lb_val,
54         [x, ub, lb, diff, inv_logit_x](auto& vi) mutable {
55           if (!is_constant<T>::value) {
56             forward_as<var>(x).adj()
57                 += vi.adj() * diff * inv_logit_x * (1.0 - inv_logit_x);
58           }
59           if (!is_constant<L>::value) {
60             forward_as<var>(lb).adj() += vi.adj() * (1.0 - inv_logit_x);
61           }
62           if (!is_constant<U>::value) {
63             forward_as<var>(ub).adj() += vi.adj() * inv_logit_x;
64           }
65         });
66   }
67 }
68 
69 /**
70  * Return the lower- and upper-bounded scalar derived by
71  * transforming the specified free scalar given the specified
72  * lower and upper bounds and increment the specified log
73  * density with the log absolute Jacobian determinant.
74  *
75  * <p>The transform is as defined in
76  * <code>lub_constrain(T, double, double)</code>.  The log absolute
77  * Jacobian determinant is given by
78  *
79  * <p>\f$\log \left| \frac{d}{dx} \left(
80  *                L + (U-L) \mbox{logit}^{-1}(x) \right)
81  *            \right|\f$
82  *
83  * <p>\f$ {} = \log |
84  *         (U-L)
85  *         \, (\mbox{logit}^{-1}(x))
86  *         \, (1 - \mbox{logit}^{-1}(x)) |\f$
87  *
88  * <p>\f$ {} = \log (U - L) + \log (\mbox{logit}^{-1}(x))
89  *                          + \log (1 - \mbox{logit}^{-1}(x))\f$
90  *
91  * @tparam T Scalar.
92  * @tparam L Scalar.
93  * @tparam U Scalar.
94  * @param[in] x Free scalar to transform.
95  * @param[in] lb Lower bound.
96  * @param[in] ub Upper bound.
97  * @param[in,out] lp Log probability scalar reference.
98  * @return Lower- and upper-bounded scalar derived from transforming
99  *   the free scalar.
100  * @throw std::domain_error if ub <= lb
101  */
102 template <typename T, typename L, typename U,
103           require_all_stan_scalar_t<T, L, U>* = nullptr,
104           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub,return_type_t<T,L,U> & lp)105 inline auto lub_constrain(const T& x, const L& lb, const U& ub,
106                           return_type_t<T, L, U>& lp) {
107   using std::exp;
108   const auto lb_val = value_of(lb);
109   const auto ub_val = value_of(ub);
110   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
111   const bool is_ub_inf = ub_val == INFTY;
112   if (unlikely(is_ub_inf && is_lb_inf)) {
113     return identity_constrain(x, ub, lb);
114   } else if (unlikely(is_ub_inf)) {
115     return lb_constrain(identity_constrain(x, ub), lb, lp);
116   } else if (unlikely(is_lb_inf)) {
117     return ub_constrain(identity_constrain(x, lb), ub, lp);
118   } else {
119     check_less("lub_constrain", "lb", lb_val, ub_val);
120     auto neg_abs_x = -abs(value_of(x));
121     auto diff = ub_val - lb_val;
122     double inv_logit_x = inv_logit(value_of(x));
123     lp += (log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x))));
124     return make_callback_var(
125         diff * inv_logit_x + lb_val,
126         [x, ub, lb, diff, lp, inv_logit_x](auto& vi) mutable {
127           if (!is_constant<T>::value) {
128             forward_as<var>(x).adj()
129                 += vi.adj() * diff * inv_logit_x * (1.0 - inv_logit_x)
130                    + lp.adj() * (1.0 - 2.0 * inv_logit_x);
131           }
132           if (!is_constant<L>::value && !is_constant<U>::value) {
133             const auto one_over_diff = 1.0 / diff;
134             forward_as<var>(lb).adj()
135                 += vi.adj() * (1.0 - inv_logit_x) + -one_over_diff * lp.adj();
136             forward_as<var>(ub).adj()
137                 += vi.adj() * inv_logit_x + one_over_diff * lp.adj();
138           } else if (!is_constant<L>::value) {
139             forward_as<var>(lb).adj()
140                 += vi.adj() * (1.0 - inv_logit_x) + (-1.0 / diff) * lp.adj();
141           } else if (!is_constant<U>::value) {
142             forward_as<var>(ub).adj()
143                 += vi.adj() * inv_logit_x + (1.0 / diff) * lp.adj();
144           }
145         });
146   }
147 }
148 
149 /**
150  * Specialization for Eigen matrix and scalar bounds.
151  */
152 template <typename T, typename L, typename U, require_matrix_t<T>* = nullptr,
153           require_all_stan_scalar_t<L, U>* = nullptr,
154           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub)155 inline auto lub_constrain(const T& x, const L& lb, const U& ub) {
156   using std::exp;
157   using ret_type = return_var_matrix_t<T, T, L, U>;
158   const auto lb_val = value_of(lb);
159   const auto ub_val = value_of(ub);
160   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
161   const bool is_ub_inf = ub_val == INFTY;
162   if (unlikely(is_ub_inf && is_lb_inf)) {
163     return ret_type(identity_constrain(x, ub, lb));
164   } else if (unlikely(is_ub_inf)) {
165     return ret_type(lb_constrain(identity_constrain(x, ub), lb));
166   } else if (unlikely(is_lb_inf)) {
167     return ret_type(ub_constrain(identity_constrain(x, lb), ub));
168   } else {
169     arena_t<T> arena_x = x;
170     check_less("lub_constrain", "lb", lb_val, ub_val);
171     const auto diff = ub_val - lb_val;
172     auto inv_logit_x = to_arena(inv_logit(arena_x.val().array()));
173     arena_t<ret_type> ret = diff * inv_logit_x + lb_val;
174     reverse_pass_callback([arena_x, ub, lb, ret, diff, inv_logit_x]() mutable {
175       if (!is_constant<T>::value) {
176         using T_var = arena_t<promote_scalar_t<var, T>>;
177         forward_as<T_var>(arena_x).adj().array()
178             += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x);
179       }
180       if (!is_constant<L>::value) {
181         forward_as<var>(lb).adj()
182             += (ret.adj().array() * (1.0 - inv_logit_x)).sum();
183       }
184       if (!is_constant<U>::value) {
185         forward_as<var>(ub).adj() += (ret.adj().array() * inv_logit_x).sum();
186       }
187     });
188     return ret_type(ret);
189   }
190 }
191 
192 /**
193  * Specialization for Eigen matrix and scalar bounds plus lp.
194  */
195 template <typename T, typename L, typename U, require_matrix_t<T>* = nullptr,
196           require_all_stan_scalar_t<L, U>* = nullptr,
197           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub,return_type_t<T,L,U> & lp)198 inline auto lub_constrain(const T& x, const L& lb, const U& ub,
199                           return_type_t<T, L, U>& lp) {
200   using std::exp;
201   using ret_type = return_var_matrix_t<T, T, L, U>;
202   const auto lb_val = value_of(lb);
203   const auto ub_val = value_of(ub);
204   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
205   const bool is_ub_inf = ub_val == INFTY;
206   if (unlikely(is_ub_inf && is_lb_inf)) {
207     return ret_type(identity_constrain(x, ub, lb));
208   } else if (unlikely(is_ub_inf)) {
209     return ret_type(lb_constrain(identity_constrain(x, ub), lb, lp));
210   } else if (unlikely(is_lb_inf)) {
211     return ret_type(ub_constrain(identity_constrain(x, lb), ub, lp));
212   } else {
213     check_less("lub_constrain", "lb", lb_val, ub_val);
214     arena_t<T> arena_x = x;
215     auto neg_abs_x = to_arena(-(value_of(arena_x).array()).abs());
216     auto diff = ub_val - lb_val;
217     lp += (log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x)))).sum();
218     auto inv_logit_x = to_arena(inv_logit(value_of(arena_x).array()));
219     arena_t<ret_type> ret = diff * inv_logit_x + lb_val;
220     reverse_pass_callback(
221         [arena_x, ub, lb, ret, lp, diff, inv_logit_x]() mutable {
222           if (!is_constant<T>::value) {
223             forward_as<arena_t<promote_scalar_t<var, T>>>(arena_x).adj().array()
224                 += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x)
225                    + lp.adj() * (1.0 - 2.0 * inv_logit_x);
226           }
227           if (!is_constant<L>::value && !is_constant<U>::value) {
228             const auto lp_calc = lp.adj() * ret.size();
229             const auto one_over_diff = 1.0 / diff;
230             forward_as<var>(lb).adj()
231                 += (ret.adj().array() * (1.0 - inv_logit_x)).sum()
232                    + -one_over_diff * lp_calc;
233             forward_as<var>(ub).adj() += (ret.adj().array() * inv_logit_x).sum()
234                                          + one_over_diff * lp_calc;
235           } else if (!is_constant<L>::value) {
236             forward_as<var>(lb).adj()
237                 += (ret.adj().array() * (1.0 - inv_logit_x)).sum()
238                    + -(1.0 / diff) * lp.adj() * ret.size();
239           } else if (!is_constant<U>::value) {
240             forward_as<var>(ub).adj() += (ret.adj().array() * inv_logit_x).sum()
241                                          + (1.0 / diff) * lp.adj() * ret.size();
242           }
243         });
244     return ret_type(ret);
245   }
246 }
247 
248 /**
249  * Specialization for Eigen matrix with matrix lower bound and scalar upper
250  * bound.
251  */
252 template <typename T, typename L, typename U,
253           require_all_matrix_t<T, L>* = nullptr,
254           require_stan_scalar_t<U>* = nullptr,
255           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub)256 inline auto lub_constrain(const T& x, const L& lb, const U& ub) {
257   using std::exp;
258   using ret_type = return_var_matrix_t<T, T, L, U>;
259   const auto ub_val = value_of(ub);
260   const bool is_ub_inf = ub_val == INFTY;
261   if (unlikely(is_ub_inf)) {
262     return eval(lb_constrain(identity_constrain(x, ub), lb));
263   } else {
264     arena_t<T> arena_x = x;
265     arena_t<L> arena_lb = lb;
266     const auto lb_val = value_of(arena_lb).array().eval();
267     check_less("lub_constrain", "lb", lb_val, ub_val);
268     auto is_lb_inf = to_arena((lb_val == NEGATIVE_INFTY));
269     auto diff = to_arena(ub_val - lb_val);
270     auto inv_logit_x = to_arena(inv_logit(value_of(arena_x).array()));
271     arena_t<ret_type> ret = (is_lb_inf).select(
272         ub_val - value_of(arena_x).array().exp(), diff * inv_logit_x + lb_val);
273     reverse_pass_callback([arena_x, ub, arena_lb, ret, diff, inv_logit_x,
274                            is_lb_inf]() mutable {
275       using T_var = arena_t<promote_scalar_t<var, T>>;
276       using L_var = arena_t<promote_scalar_t<var, L>>;
277       if (!is_constant<T>::value) {
278         forward_as<T_var>(arena_x).adj().array() += (is_lb_inf).select(
279             ret.adj().array() * -value_of(arena_x).array().exp(),
280             ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x));
281       }
282       if (!is_constant<U>::value) {
283         forward_as<var>(ub).adj()
284             += (is_lb_inf)
285                    .select(ret.adj().array(), ret.adj().array() * inv_logit_x)
286                    .sum();
287       }
288       if (!is_constant<L>::value) {
289         forward_as<L_var>(arena_lb).adj().array()
290             += (is_lb_inf).select(0, ret.adj().array() * (1.0 - inv_logit_x));
291       }
292     });
293     return ret_type(ret);
294   }
295 }
296 
297 /**
298  * Specialization for Eigen matrix with matrix lower bound and scalar upper
299  * bound plus lp.
300  */
301 template <typename T, typename L, typename U,
302           require_all_matrix_t<T, L>* = nullptr,
303           require_stan_scalar_t<U>* = nullptr,
304           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub,std::decay_t<return_type_t<T,L,U>> & lp)305 inline auto lub_constrain(const T& x, const L& lb, const U& ub,
306                           std::decay_t<return_type_t<T, L, U>>& lp) {
307   using std::exp;
308   using ret_type = return_var_matrix_t<T, T, L, U>;
309   const auto ub_val = value_of(ub);
310   const bool is_ub_inf = ub_val == INFTY;
311   if (unlikely(is_ub_inf)) {
312     return ret_type(lb_constrain(identity_constrain(x, ub), lb, lp));
313   } else {
314     arena_t<T> arena_x = x;
315     arena_t<L> arena_lb = lb;
316     const auto x_val = value_of(arena_x).array();
317     const auto lb_val = value_of(arena_lb).array().eval();
318     check_less("lub_constrain", "lb", lb_val, ub_val);
319     auto is_lb_inf = to_arena((lb_val == NEGATIVE_INFTY));
320     auto diff = to_arena(ub_val - lb_val);
321     auto neg_abs_x = to_arena(-(value_of(arena_x).array()).abs());
322     auto inv_logit_x = to_arena(inv_logit(value_of(arena_x).array()));
323     arena_t<ret_type> ret = (is_lb_inf).select(
324         ub_val - value_of(arena_x).array().exp(), diff * inv_logit_x + lb_val);
325     lp += (is_lb_inf)
326               .select(value_of(arena_x).array(),
327                       log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x))))
328               .sum();
329     reverse_pass_callback([arena_x, ub, arena_lb, ret, lp, diff, inv_logit_x,
330                            is_lb_inf]() mutable {
331       using T_var = arena_t<promote_scalar_t<var, T>>;
332       using L_var = arena_t<promote_scalar_t<var, L>>;
333       const auto lp_adj = lp.adj();
334       if (!is_constant<T>::value) {
335         const auto x_sign = value_of(arena_x).array().sign().eval();
336         forward_as<T_var>(arena_x).adj().array() += (is_lb_inf).select(
337             ret.adj().array() * -value_of(arena_x).array().exp() + lp_adj,
338             ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x)
339                 + lp.adj() * (1.0 - 2.0 * inv_logit_x));
340       }
341       if (!is_constant<L>::value) {
342         forward_as<L_var>(arena_lb).adj().array()
343             += (is_lb_inf).select(0, ret.adj().array() * (1.0 - inv_logit_x)
344                                          + -(1.0 / diff) * lp_adj);
345       }
346       if (!is_constant<U>::value) {
347         forward_as<var>(ub).adj()
348             += (is_lb_inf)
349                    .select(ret.adj().array(), ret.adj().array() * inv_logit_x
350                                                   + (1.0 / diff) * lp_adj)
351                    .sum();
352       }
353     });
354     return ret_type(ret);
355   }
356 }
357 
358 /**
359  * Specialization for Eigen matrix with scalar lower bound and matrix upper
360  * bound.
361  */
362 template <typename T, typename L, typename U,
363           require_all_matrix_t<T, U>* = nullptr,
364           require_stan_scalar_t<L>* = nullptr,
365           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub)366 inline auto lub_constrain(const T& x, const L& lb, const U& ub) {
367   using std::exp;
368   using ret_type = return_var_matrix_t<T, T, L, U>;
369   const auto lb_val = value_of(lb);
370   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
371   if (unlikely(is_lb_inf)) {
372     return eval(ub_constrain(identity_constrain(x, lb), ub));
373   } else {
374     arena_t<T> arena_x = x;
375     auto arena_x_val = to_arena(value_of(arena_x));
376     arena_t<U> arena_ub = ub;
377     const auto ub_val = value_of(arena_ub).array().eval();
378     check_less("lub_constrain", "lb", lb_val, ub_val);
379     auto is_ub_inf = to_arena((ub_val == INFTY));
380     auto diff = to_arena(ub_val - lb_val);
381     auto inv_logit_x = to_arena(inv_logit(arena_x_val.array()));
382     arena_t<ret_type> ret = (is_ub_inf).select(
383         arena_x_val.array().exp() + lb_val, diff * inv_logit_x + lb_val);
384     reverse_pass_callback([arena_x, arena_x_val, arena_ub, lb, ret, is_ub_inf,
385                            inv_logit_x, diff]() mutable {
386       using T_var = arena_t<promote_scalar_t<var, T>>;
387       using U_var = arena_t<promote_scalar_t<var, U>>;
388       if (!is_constant<T>::value) {
389         forward_as<T_var>(arena_x).adj().array() += (is_ub_inf).select(
390             ret.adj().array() * arena_x_val.array().exp(),
391             ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x));
392       }
393       if (!is_constant<L>::value) {
394         forward_as<var>(lb).adj()
395             += (is_ub_inf)
396                    .select(ret.adj().array(),
397                            ret.adj().array() * (1.0 - inv_logit_x))
398                    .sum();
399       }
400       if (!is_constant<U>::value) {
401         forward_as<U_var>(arena_ub).adj().array()
402             += (is_ub_inf).select(0, ret.adj().array() * inv_logit_x);
403       }
404     });
405     return ret_type(ret);
406   }
407 }
408 
409 /**
410  * Specialization for Eigen matrix with scalar lower bound and matrix upper
411  * bound plus lp.
412  */
413 template <typename T, typename L, typename U,
414           require_all_matrix_t<T, U>* = nullptr,
415           require_stan_scalar_t<L>* = nullptr,
416           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub,std::decay_t<return_type_t<T,L,U>> & lp)417 inline auto lub_constrain(const T& x, const L& lb, const U& ub,
418                           std::decay_t<return_type_t<T, L, U>>& lp) {
419   using std::exp;
420   using ret_type = return_var_matrix_t<T, T, L, U>;
421   const auto lb_val = value_of(lb);
422   const bool is_lb_inf = lb_val == NEGATIVE_INFTY;
423   if (unlikely(is_lb_inf)) {
424     return eval(ub_constrain(identity_constrain(x, lb), ub, lp));
425   } else {
426     arena_t<T> arena_x = x;
427     auto arena_x_val = to_arena(value_of(arena_x));
428     arena_t<U> arena_ub = ub;
429     const auto& ub_val = to_ref(value_of(arena_ub));
430     check_less("lub_constrain", "lb", lb_val, ub_val);
431     auto is_ub_inf = to_arena((ub_val.array() == INFTY));
432     auto diff = to_arena(ub_val.array() - lb_val);
433     auto neg_abs_x = to_arena(-(arena_x_val.array()).abs());
434     lp += (is_ub_inf)
435               .select(arena_x_val.array(),
436                       log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x))))
437               .sum();
438     auto inv_logit_x = to_arena(inv_logit(arena_x_val.array()));
439     arena_t<ret_type> ret = (is_ub_inf).select(
440         arena_x_val.array().exp() + lb_val, diff * inv_logit_x + lb_val);
441     reverse_pass_callback([arena_x, arena_x_val, diff, inv_logit_x, arena_ub,
442                            lb, ret, lp, is_ub_inf]() mutable {
443       using T_var = arena_t<promote_scalar_t<var, T>>;
444       using U_var = arena_t<promote_scalar_t<var, U>>;
445       const auto lp_adj = lp.adj();
446       if (!is_constant<T>::value) {
447         forward_as<T_var>(arena_x).adj().array() += (is_ub_inf).select(
448             ret.adj().array() * arena_x_val.array().exp() + lp_adj,
449             ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x)
450                 + lp.adj() * (1.0 - 2.0 * inv_logit_x));
451       }
452       if (!is_constant<L>::value) {
453         forward_as<var>(lb).adj()
454             += (is_ub_inf)
455                    .select(ret.adj().array(),
456                            ret.adj().array() * (1.0 - inv_logit_x)
457                                + -(1.0 / diff) * lp_adj)
458                    .sum();
459       }
460       if (!is_constant<U>::value) {
461         forward_as<U_var>(arena_ub).adj().array() += (is_ub_inf).select(
462             0, ret.adj().array() * inv_logit_x + (1.0 / diff) * lp_adj);
463       }
464     });
465     return ret_type(ret);
466   }
467 }
468 
469 /**
470  * Specialization for Eigen matrix and matrix bounds.
471  */
472 template <typename T, typename L, typename U,
473           require_all_matrix_t<T, L, U>* = nullptr,
474           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub)475 inline auto lub_constrain(const T& x, const L& lb, const U& ub) {
476   using std::exp;
477   using ret_type = return_var_matrix_t<T, T, L, U>;
478   arena_t<T> arena_x = x;
479   auto arena_x_val = value_of(arena_x);
480   arena_t<L> arena_lb = lb;
481   arena_t<U> arena_ub = ub;
482   auto lb_val = value_of(arena_lb).array();
483   auto ub_val = value_of(arena_ub).array();
484   check_less("lub_constrain", "lb", lb_val, ub_val);
485   auto inv_logit_x = to_arena(inv_logit(arena_x_val.array()));
486   auto is_lb_inf = to_arena((lb_val == NEGATIVE_INFTY));
487   auto is_ub_inf = to_arena((ub_val == INFTY));
488   auto is_lb_ub_inf = to_arena(is_lb_inf && is_ub_inf);
489   auto diff = to_arena(ub_val - lb_val);
490   // if both, identity, then lb_inf -> ub_constrain, then ub_inf -> lb_constrain
491   arena_t<ret_type> ret
492       = (is_lb_ub_inf)
493             .select(arena_x_val.array(),
494                     (is_lb_inf).select(
495                         ub_val - arena_x.val().array().exp(),
496                         (is_ub_inf).select(arena_x_val.array().exp() + lb_val,
497                                            diff * inv_logit_x + lb_val)));
498   reverse_pass_callback([arena_x, arena_x_val, inv_logit_x, arena_ub, arena_lb,
499                          diff, ret, is_ub_inf, is_lb_inf,
500                          is_lb_ub_inf]() mutable {
501     using T_var = arena_t<promote_scalar_t<var, T>>;
502     using L_var = arena_t<promote_scalar_t<var, L>>;
503     using U_var = arena_t<promote_scalar_t<var, U>>;
504     // The most likely case is neither of them are infinity
505     const bool is_none_inf = !(is_lb_inf.any() || is_ub_inf.any());
506     if (is_none_inf) {
507       if (!is_constant<T>::value) {
508         forward_as<T_var>(arena_x).adj().array()
509             += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x);
510       }
511       if (!is_constant<L>::value) {
512         forward_as<L_var>(arena_lb).adj().array()
513             += ret.adj().array() * (1.0 - inv_logit_x);
514       }
515       if (!is_constant<U>::value) {
516         forward_as<U_var>(arena_ub).adj().array()
517             += ret.adj().array() * inv_logit_x;
518       }
519     } else {
520       if (!is_constant<T>::value) {
521         forward_as<T_var>(arena_x).adj().array()
522             += (is_lb_ub_inf)
523                    .select(
524                        ret.adj().array(),
525                        (is_lb_inf).select(
526                            ret.adj().array() * -arena_x_val.array().exp(),
527                            (is_ub_inf).select(
528                                ret.adj().array() * arena_x_val.array().exp(),
529                                ret.adj().array() * diff * inv_logit_x
530                                    * (1.0 - inv_logit_x))));
531       }
532       if (!is_constant<U>::value) {
533         forward_as<U_var>(arena_ub).adj().array() += (is_ub_inf).select(
534             0, (is_lb_inf).select(ret.adj().array(),
535                                   ret.adj().array() * inv_logit_x));
536       }
537       if (!is_constant<L>::value) {
538         forward_as<L_var>(arena_lb).adj().array() += (is_lb_inf).select(
539             0, (is_ub_inf).select(ret.adj().array(),
540                                   ret.adj().array() * (1.0 - inv_logit_x)));
541       }
542     }
543   });
544   return ret_type(ret);
545 }
546 
547 /**
548  * Specialization for Eigen matrix and matrix bounds plus lp.
549  */
550 template <typename T, typename L, typename U,
551           require_all_matrix_t<T, L, U>* = nullptr,
552           require_var_t<return_type_t<T, L, U>>* = nullptr>
lub_constrain(const T & x,const L & lb,const U & ub,return_type_t<T,L,U> & lp)553 inline auto lub_constrain(const T& x, const L& lb, const U& ub,
554                           return_type_t<T, L, U>& lp) {
555   using std::exp;
556   using ret_type = return_var_matrix_t<T, T, L, U>;
557   arena_t<T> arena_x = x;
558   auto arena_x_val = value_of(arena_x);
559   arena_t<L> arena_lb = lb;
560   arena_t<U> arena_ub = ub;
561   auto lb_val = value_of(arena_lb).array();
562   auto ub_val = value_of(arena_ub).array();
563   check_less("lub_constrain", "lb", lb_val, ub_val);
564   auto inv_logit_x = to_arena(inv_logit(arena_x_val.array()));
565   auto is_lb_inf = to_arena((lb_val == NEGATIVE_INFTY));
566   auto is_ub_inf = to_arena((ub_val == INFTY));
567   auto is_lb_ub_inf = to_arena(is_lb_inf && is_ub_inf);
568   auto diff = to_arena(ub_val - lb_val);
569   // if both, identity, then lb_inf -> ub_constrain, then ub_inf -> lb_constrain
570   arena_t<ret_type> ret
571       = (is_lb_ub_inf)
572             .select(arena_x_val.array(),
573                     (is_lb_inf).select(
574                         ub_val - arena_x.val().array().exp(),
575                         (is_ub_inf).select(arena_x_val.array().exp() + lb_val,
576                                            diff * inv_logit_x + lb_val)));
577   auto neg_abs_x = to_arena(-(arena_x_val.array()).abs());
578   lp += (is_lb_ub_inf)
579             .select(
580                 0,
581                 (is_lb_inf || is_ub_inf)
582                     .select(
583                         arena_x_val.array(),
584                         log(diff) + (neg_abs_x - (2.0 * log1p_exp(neg_abs_x)))))
585             .sum();
586   reverse_pass_callback([arena_x, arena_x_val, inv_logit_x, arena_ub, arena_lb,
587                          diff, ret, is_ub_inf, is_lb_inf, is_lb_ub_inf,
588                          lp]() mutable {
589     using T_var = arena_t<promote_scalar_t<var, T>>;
590     using L_var = arena_t<promote_scalar_t<var, L>>;
591     using U_var = arena_t<promote_scalar_t<var, U>>;
592     const auto lp_adj = lp.adj();
593     // The most likely case is neither of them are infinity
594     const bool is_none_inf = !(is_lb_inf.any() || is_ub_inf.any());
595     if (is_none_inf) {
596       if (!is_constant<T>::value) {
597         forward_as<T_var>(arena_x).adj().array()
598             += ret.adj().array() * diff * inv_logit_x * (1.0 - inv_logit_x)
599                + lp.adj() * (1.0 - 2.0 * inv_logit_x);
600       }
601       if (!is_constant<L>::value) {
602         forward_as<L_var>(arena_lb).adj().array()
603             += ret.adj().array() * (1.0 - inv_logit_x) + -(1.0 / diff) * lp_adj;
604       }
605       if (!is_constant<U>::value) {
606         forward_as<U_var>(arena_ub).adj().array()
607             += ret.adj().array() * inv_logit_x + (1.0 / diff) * lp_adj;
608       }
609     } else {
610       if (!is_constant<T>::value) {
611         forward_as<T_var>(arena_x).adj().array()
612             += (is_lb_ub_inf)
613                    .select(
614                        ret.adj().array(),
615                        (is_lb_inf).select(
616                            ret.adj().array() * -arena_x_val.array().exp()
617                                + lp_adj,
618                            (is_ub_inf).select(
619                                ret.adj().array() * arena_x_val.array().exp()
620                                    + lp_adj,
621                                ret.adj().array() * diff * inv_logit_x
622                                        * (1.0 - inv_logit_x)
623                                    + lp.adj() * (1.0 - 2.0 * inv_logit_x))));
624       }
625       if (!is_constant<L>::value) {
626         forward_as<L_var>(arena_lb).adj().array() += (is_lb_inf).select(
627             0, (is_ub_inf).select(ret.adj().array(),
628                                   ret.adj().array() * (1.0 - inv_logit_x)
629                                       + -(1.0 / diff) * lp_adj));
630       }
631       if (!is_constant<U>::value) {
632         forward_as<U_var>(arena_ub).adj().array() += (is_ub_inf).select(
633             0, (is_lb_inf).select(
634                    ret.adj().array(),
635                    ret.adj().array() * inv_logit_x + (1.0 / diff) * lp_adj));
636       }
637     }
638   });
639   return ret_type(ret);
640 }
641 
642 }  // namespace math
643 }  // namespace stan
644 
645 #endif
646