use super::{annuity_factor, compound_factor, validate_financial_params};
use crate::array::Array;
use crate::error::{NumRs2Error, Result};
#[allow(unused_imports)] use num_traits::{Float, One, Zero};
use std::fmt::Debug;
pub fn pmt<T>(rate: T, nper: T, pv: T, fv: T, when: i32) -> Result<T>
where
T: Float + Debug + Clone,
{
validate_financial_params(rate, nper, T::zero(), pv, fv)?;
let when_factor = if when == 1 { T::one() + rate } else { T::one() };
if rate.is_zero() {
return Ok(-(pv + fv) / nper);
}
let compound = compound_factor(rate, nper);
let annuity = annuity_factor(rate, nper);
let numerator = pv * compound + fv;
let denominator = annuity * when_factor;
Ok(-numerator / denominator)
}
pub fn pmt_array<T>(
rate: &Array<T>,
nper: &Array<T>,
pv: &Array<T>,
fv: &Array<T>,
when: i32,
) -> Result<Array<T>>
where
T: Float + Debug + Clone,
{
if rate.shape() != nper.shape() || rate.shape() != pv.shape() || rate.shape() != fv.shape() {
return Err(NumRs2Error::DimensionMismatch(
"All input arrays must have the same shape".to_string(),
));
}
let rate_vec = rate.to_vec();
let nper_vec = nper.to_vec();
let pv_vec = pv.to_vec();
let fv_vec = fv.to_vec();
let mut result_vec = Vec::with_capacity(rate_vec.len());
for i in 0..rate_vec.len() {
let pmt_result = pmt(rate_vec[i], nper_vec[i], pv_vec[i], fv_vec[i], when)?;
result_vec.push(pmt_result);
}
Ok(Array::from_vec(result_vec).reshape(&rate.shape()))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_pmt_basic_loan() {
let monthly_rate = 0.05 / 12.0;
let months = 5.0 * 12.0;
let result =
pmt(monthly_rate, months, 10000.0, 0.0, 0).expect("pmt calculation should succeed");
assert_relative_eq!(result, -188.7107, epsilon = 1e-2);
}
#[test]
fn test_pmt_with_future_value() {
let result = pmt(0.05, 10.0, 0.0, 10000.0, 0).expect("pmt calculation should succeed");
assert_relative_eq!(result, -795.04, epsilon = 1e-2);
}
#[test]
fn test_pmt_beginning_of_period() {
let monthly_rate = 0.05 / 12.0;
let months = 5.0 * 12.0;
let result =
pmt(monthly_rate, months, 10000.0, 0.0, 1).expect("pmt calculation should succeed");
assert_relative_eq!(result, -187.93, epsilon = 1e-2);
}
#[test]
fn test_pmt_zero_rate() {
let result = pmt(0.0, 10.0, 1000.0, 0.0, 0).expect("pmt calculation should succeed");
assert_relative_eq!(result, -100.0, epsilon = 1e-9);
}
#[test]
fn test_pmt_savings() {
let result = pmt(0.05, 10.0, 0.0, 10000.0, 0).expect("pmt calculation should succeed");
assert_relative_eq!(result, -795.04, epsilon = 1e-2);
}
#[test]
fn test_pmt_array() {
let rates = Array::from_vec(vec![0.05 / 12.0, 0.06 / 12.0]);
let npers = Array::from_vec(vec![60.0, 72.0]);
let pvs = Array::from_vec(vec![10000.0, 15000.0]);
let fvs = Array::from_vec(vec![0.0, 0.0]);
let result =
pmt_array(&rates, &npers, &pvs, &fvs, 0).expect("pmt_array calculation should succeed");
assert_eq!(result.shape(), vec![2]);
let values = result.to_vec();
assert_relative_eq!(values[0], -188.7107, epsilon = 1e-2);
assert_relative_eq!(values[1], -248.59, epsilon = 1e-2);
}
}