-->

PYTHON - Bisection search MIT Intro to programming

2019-06-13 22:46发布

问题:

I am trying to find the best rate of savings to achieve a down payment on a $1million house in 36 months. savings need to be within 100$ of required down payment. downpayment is 25% of total cost. I have to search for an integer between 0 and 10000 (using integer division), and then convert it to a decimal percentage (using float division) to use when we are calculating the current_savings​ after 36 months.

This is my code which is not working (I am really new to programming)

annual_salary = 150000
total_cost = 1000000
low=0
high=10000
portion_saved=(low+high)/20000.0
epsilon=100
current_savings = 0
portion_down_payment=0.25*total_cost
steps = 0
rate = 0.04
number_of_months = 0
semi_annual_raise = 0.07

while current_savings-portion_down_payment>=epsilon and number_of_months<=36:
current_savings += annual_salary * portion_saved / 12
current_savings += current_savings * rate / 12
number_of_months += 1
if number_of_months % 6 == 0:
annual_salary += annual_salary * semi_annual_raise

if current_savings<portion_down_payment:
    low=portion_saved
else:
    high=portion_saved
portion_saved=(low+high)/20000.0
steps+=1

print("Best savings rate: ", portion_saved)
print("Steps in bisection search", steps)

Any help is greatly appreciated !!!

回答1:

The monthly income does not vary with savings rate, so it makes sense to only calculate it once:

# calculate income per month over 36 months
base_monthly_salary = 150000 // 12
semiannual_raise = 0.07
monthly_incomes = [base_monthly_salary * (1. + semiannual_raise) ** (month // 6) for month in range(36)]

If the monthly savings do not earn interest, the problem is trivial:

target_amount = 1000000. * 0.25
savings_rate = target_amount / sum(monthly_incomes)    # 0.4659859

so you would have to save 46.6% of income.

If monthly savings earn interest, the problem is more interesting (bad pun absolutely intended).

def savings_balance(monthly_incomes, monthly_interest_rate, savings_rate):
    total = 0.
    for amt in monthly_incomes:
        # At the end of each month,
        total *= 1. + monthly_interest_rate  # we earn interest on what was already saved
        total += amt * savings_rate          # and add a fixed fraction of our monthly income
    return total

Let's test it based on our calculation above,

savings_balance(monthly_incomes, 0.0, 0.4659859)   # 249999.9467

so that looks like what we expected.

You can think of this function as iteratively evaluating a 36th-degree polynomial. Given known monthly_incomes and interest_rate, we want to find savings_rate to produce a desired total, ie find the only real positive root of polynomial - target == 0. There is no analytic solution if interest_rate > 0., so we will try for a numeric solution.

target_amount = 1000000. * 0.25

# Make up a number: annual savings interest = 1.9%
monthly_interest_rate = 0.019 / 12.

# Our solver expects a single-argument function to solve, so let's make it one:
def fn(x):
    return savings_balance(monthly_incomes, monthly_interest_rate, x)

def bisection_search(fn, lo, hi, target, tolerance=0.1):
    # This function assumes that fn is monotonically increasing!

    # check the end-points - is a solution possible?
    fn_lo = fn(lo)
    assert not target < -tolerance + fn_lo, "target is unattainably low"
    if abs(target - fn_lo) <= tolerance:
        return lo
    fn_hi = fn(hi)
    assert not fn_hi + tolerance < target, "target is unattainably high"
    if abs(target - fn_hi) <= tolerance:
        return hi

    # a solution is possible but not yet found -
    #   repeat until we find it
    while True:
        # test the middle of the target range
        mid = (lo + hi) / 2
        fn_mid = fn(mid)
        # is this an acceptable solution?
        if abs(target - fn_mid) <= tolerance:
            return mid
        else:
            # do we need to look in the lower or upper half?
            if target < fn_mid:
                # look lower - bring the top down
                hi = mid
            else:
                # look higher - bring the bottom up
                lo = mid

and now we run it like

# From above, we know that
# when interest = 0.0 we need a savings rate of 46.6%
#
# If interest > 0. the savings_rate should be smaller,
# because some of target_amount will be covered by generated interest.
#
# For a small annual_interest_rate over an N year term,
# the effective accrued interest rate will be close to
# N * annual_interest_rate / 2  ->  1.5 * 1.9% == 2.85%
#
# So we expect the required savings rate to be
# about 46.6% * (1. - 0.0285) == 45.3%

bisection_search(fn, 0.40, 0.47, target_amount)  # 0.454047973

which gives a savings rate of 45.4%.