owlplanner 2025.3.12__py3-none-any.whl → 2025.3.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- owlplanner/plan.py +40 -20
- owlplanner/utils.py +9 -2
- owlplanner/version.py +1 -1
- {owlplanner-2025.3.12.dist-info → owlplanner-2025.3.14.dist-info}/METADATA +1 -1
- {owlplanner-2025.3.12.dist-info → owlplanner-2025.3.14.dist-info}/RECORD +7 -7
- {owlplanner-2025.3.12.dist-info → owlplanner-2025.3.14.dist-info}/WHEEL +0 -0
- {owlplanner-2025.3.12.dist-info → owlplanner-2025.3.14.dist-info}/licenses/LICENSE +0 -0
owlplanner/plan.py
CHANGED
|
@@ -34,7 +34,7 @@ from owlplanner import logging
|
|
|
34
34
|
from owlplanner import progress
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
# This makes all graphs
|
|
37
|
+
# This makes all graphs to have the same height.
|
|
38
38
|
plt.rcParams.update({'figure.autolayout': True})
|
|
39
39
|
|
|
40
40
|
|
|
@@ -266,6 +266,9 @@ class Plan(object):
|
|
|
266
266
|
# self.horizons = [yobs[i] + expectancy[i] - thisyear + 1 for i in range(self.N_i)]
|
|
267
267
|
self.N_n = np.max(self.horizons)
|
|
268
268
|
self.year_n = np.linspace(thisyear, thisyear + self.N_n - 1, self.N_n, dtype=np.int32)
|
|
269
|
+
# Year in the plan (if any) where individuals turn 59. For 10% withdrawal penalty.
|
|
270
|
+
self.n59 = 59 - thisyear + self.yobs
|
|
271
|
+
self.n59[self.n59 < 0] = 0
|
|
269
272
|
# Handle passing of one spouse before the other.
|
|
270
273
|
if self.N_i == 2 and np.min(self.horizons) != np.max(self.horizons):
|
|
271
274
|
self.n_d = np.min(self.horizons)
|
|
@@ -1289,6 +1292,11 @@ class Plan(object):
|
|
|
1289
1292
|
for t in range(Nt):
|
|
1290
1293
|
row.addElem(_q2(CF, t, n, Nt, Nn), self.theta_tn[t, n])
|
|
1291
1294
|
|
|
1295
|
+
# Minus 10% penalty on early withdrawals.
|
|
1296
|
+
if n < self.n59[i]:
|
|
1297
|
+
row.addElem(_q3(Cw, i, 1, n, Ni, Nj, Nn), 0.1)
|
|
1298
|
+
row.addElem(_q3(Cw, i, 2, n, Ni, Nj, Nn), 0.1)
|
|
1299
|
+
|
|
1292
1300
|
A.addRow(row, rhs, rhs)
|
|
1293
1301
|
|
|
1294
1302
|
# Impose income profile.
|
|
@@ -1970,6 +1978,12 @@ class Plan(object):
|
|
|
1970
1978
|
self.G_n = np.sum(self.F_tn, axis=0)
|
|
1971
1979
|
self.T_tn = self.F_tn * self.theta_tn
|
|
1972
1980
|
self.T_n = np.sum(self.T_tn, axis=0)
|
|
1981
|
+
self.penalty_n = np.zeros(Nn)
|
|
1982
|
+
# Add early withdrawal penalties if any.
|
|
1983
|
+
for i in range(Ni):
|
|
1984
|
+
self.penalty_n[0:self.n59[i]] += 0.1*(self.w_ijn[i, 1, 0:self.n59[i]] + self.w_ijn[i, 2, 0:self.n59[i]])
|
|
1985
|
+
|
|
1986
|
+
self.T_n += self.penalty_n
|
|
1973
1987
|
|
|
1974
1988
|
tau_0 = np.array(self.tau_kn[0, :])
|
|
1975
1989
|
tau_0[tau_0 < 0] = 0
|
|
@@ -2094,33 +2108,38 @@ class Plan(object):
|
|
|
2094
2108
|
totIncome = np.sum(self.g_n, axis=0)
|
|
2095
2109
|
totIncomeNow = np.sum(self.g_n / self.gamma_n[:-1], axis=0)
|
|
2096
2110
|
dic["Total net spending"] = f"{u.d(totIncomeNow)}"
|
|
2097
|
-
dic["
|
|
2111
|
+
dic["[Total net spending]"] = f"{u.d(totIncome)}"
|
|
2098
2112
|
|
|
2099
2113
|
totRoth = np.sum(self.x_in, axis=(0, 1))
|
|
2100
2114
|
totRothNow = np.sum(np.sum(self.x_in, axis=0) / self.gamma_n[:-1], axis=0)
|
|
2101
2115
|
dic["Total Roth conversions"] = f"{u.d(totRothNow)}"
|
|
2102
|
-
dic["
|
|
2116
|
+
dic["[Total Roth conversions]"] = f"{u.d(totRoth)}"
|
|
2103
2117
|
|
|
2104
2118
|
taxPaid = np.sum(self.T_n, axis=0)
|
|
2105
2119
|
taxPaidNow = np.sum(self.T_n / self.gamma_n[:-1], axis=0)
|
|
2106
2120
|
dic["Total income tax paid on ordinary income"] = f"{u.d(taxPaidNow)}"
|
|
2107
|
-
dic["
|
|
2121
|
+
dic["[Total income tax paid on ordinary income]"] = f"{u.d(taxPaid)}"
|
|
2108
2122
|
for t in range(self.N_t):
|
|
2109
2123
|
taxPaid = np.sum(self.T_tn[t], axis=0)
|
|
2110
2124
|
taxPaidNow = np.sum(self.T_tn[t] / self.gamma_n[:-1], axis=0)
|
|
2111
2125
|
tname = tx.taxBracketNames[t]
|
|
2112
2126
|
dic[f"-- Subtotal in tax bracket {tname}"] = f"{u.d(taxPaidNow)}"
|
|
2113
|
-
dic[f"
|
|
2127
|
+
dic[f"-- [Subtotal in tax bracket {tname}]"] = f"{u.d(taxPaid)}"
|
|
2128
|
+
|
|
2129
|
+
penaltyPaid = np.sum(self.penalty_n, axis=0)
|
|
2130
|
+
penaltyPaidNow = np.sum(self.penalty_n / self.gamma_n[:-1], axis=0)
|
|
2131
|
+
dic["-- Subtotal in early withdrawal penalty"] = f"{u.d(penaltyPaidNow)}"
|
|
2132
|
+
dic["-- [Subtotal in early withdrawal penalty]"] = f"{u.d(penaltyPaid)}"
|
|
2114
2133
|
|
|
2115
2134
|
taxPaid = np.sum(self.U_n, axis=0)
|
|
2116
2135
|
taxPaidNow = np.sum(self.U_n / self.gamma_n[:-1], axis=0)
|
|
2117
2136
|
dic["Total tax paid on gains and dividends"] = f"{u.d(taxPaidNow)}"
|
|
2118
|
-
dic["
|
|
2137
|
+
dic["[Total tax paid on gains and dividends]"] = f"{u.d(taxPaid)}"
|
|
2119
2138
|
|
|
2120
2139
|
taxPaid = np.sum(self.M_n, axis=0)
|
|
2121
2140
|
taxPaidNow = np.sum(self.M_n / self.gamma_n[:-1], axis=0)
|
|
2122
2141
|
dic["Total Medicare premiums paid"] = f"{u.d(taxPaidNow)}"
|
|
2123
|
-
dic["
|
|
2142
|
+
dic["[Total Medicare premiums paid]"] = f"{u.d(taxPaid)}"
|
|
2124
2143
|
|
|
2125
2144
|
if self.N_i == 2 and self.n_d < self.N_n:
|
|
2126
2145
|
p_j = self.partialEstate_j * (1 - self.phi_j)
|
|
@@ -2135,24 +2154,24 @@ class Plan(object):
|
|
|
2135
2154
|
iname_s = self.inames[self.i_s]
|
|
2136
2155
|
iname_d = self.inames[self.i_d]
|
|
2137
2156
|
dic[f"Sum of spousal transfer to {iname_s} in year {ynx}"] = (f"{u.d(totSpousalNow)}")
|
|
2138
|
-
dic[f"
|
|
2157
|
+
dic[f"[Sum of spousal transfer to {iname_s} in year {ynx}]"] = (
|
|
2139
2158
|
f"{u.d(totSpousal)}")
|
|
2140
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - taxable
|
|
2159
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - taxable]"] = (
|
|
2141
2160
|
f"{u.d(q_j[0])}")
|
|
2142
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-def
|
|
2161
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - tax-def]"] = (
|
|
2143
2162
|
f"{u.d(q_j[1])}")
|
|
2144
|
-
dic[f"-- Spousal transfer to {iname_s} in year {ynx} - tax-free
|
|
2163
|
+
dic[f"-- [Spousal transfer to {iname_s} in year {ynx} - tax-free]"] = (
|
|
2145
2164
|
f"{u.d(q_j[2])}")
|
|
2146
2165
|
|
|
2147
2166
|
dic[f"Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}"] = (
|
|
2148
2167
|
f"{u.d(totOthersNow)}")
|
|
2149
|
-
dic[f"
|
|
2168
|
+
dic[f"[Sum of post-tax non-spousal bequests from {iname_d} in year {ynx}]"] = (
|
|
2150
2169
|
f"{u.d(totOthers)}")
|
|
2151
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable
|
|
2170
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - taxable]"] = (
|
|
2152
2171
|
f"{u.d(p_j[0])}")
|
|
2153
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-def
|
|
2172
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-def]"] = (
|
|
2154
2173
|
f"{u.d(p_j[1])}")
|
|
2155
|
-
dic[f"-- Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-free
|
|
2174
|
+
dic[f"-- [Post-tax non-spousal bequests from {iname_d} in year {ynx} - tax-free]"] = (
|
|
2156
2175
|
f"{u.d(p_j[2])}")
|
|
2157
2176
|
|
|
2158
2177
|
estate = np.sum(self.b_ijn[:, :, self.N_n], axis=0)
|
|
@@ -2161,10 +2180,10 @@ class Plan(object):
|
|
|
2161
2180
|
totEstate = np.sum(estate)
|
|
2162
2181
|
totEstateNow = totEstate / self.gamma_n[-1]
|
|
2163
2182
|
dic[f"Total estate value at the end of {lastyear}"] = (f"{u.d(totEstateNow)}")
|
|
2164
|
-
dic[f"
|
|
2165
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - taxable
|
|
2166
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - tax-def
|
|
2167
|
-
dic[f"-- Post-tax account value at the end of {lastyear} - tax-free
|
|
2183
|
+
dic[f"[Total estate value at the end of {lastyear}]"] = (f"{u.d(totEstate)}")
|
|
2184
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - taxable]"] = (f"{u.d(estate[0])}")
|
|
2185
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - tax-def]"] = (f"{u.d(estate[1])}")
|
|
2186
|
+
dic[f"-- [Post-tax account value at the end of {lastyear} - tax-free]"] = (f"{u.d(estate[2])}")
|
|
2168
2187
|
|
|
2169
2188
|
dic["Plan starting date"] = str(self.startDate)
|
|
2170
2189
|
dic[f"Cumulative inflation factor from start date to end of {lastyear}"] = (f"{self.gamma_n[-1]:.2f}")
|
|
@@ -2507,6 +2526,7 @@ class Plan(object):
|
|
|
2507
2526
|
|
|
2508
2527
|
title = self._name + "\nRaw Income Sources"
|
|
2509
2528
|
stypes = self.sources_in.keys()
|
|
2529
|
+
# stypes = [item for item in stypes if "RothX" not in item]
|
|
2510
2530
|
|
|
2511
2531
|
if tag != "":
|
|
2512
2532
|
title += " - " + tag
|
|
@@ -2517,7 +2537,7 @@ class Plan(object):
|
|
|
2517
2537
|
else:
|
|
2518
2538
|
yformat = "\\$k (" + str(self.year_n[0]) + "\\$)"
|
|
2519
2539
|
sources_in = {}
|
|
2520
|
-
for key in
|
|
2540
|
+
for key in stypes:
|
|
2521
2541
|
sources_in[key] = self.sources_in[key] / self.gamma_n[:-1]
|
|
2522
2542
|
|
|
2523
2543
|
fig, ax = _stackPlot(
|
owlplanner/utils.py
CHANGED
|
@@ -70,8 +70,8 @@ def getUnits(units) -> int:
|
|
|
70
70
|
return fac
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
#
|
|
74
|
-
# krond = lambda a, b: 1 if a == b else 0
|
|
73
|
+
# Next two functins could be a one-line lambda functions.
|
|
74
|
+
# e.g., krond = lambda a, b: 1 if a == b else 0
|
|
75
75
|
def krond(a, b) -> int:
|
|
76
76
|
"""
|
|
77
77
|
Kronecker integer delta function.
|
|
@@ -79,6 +79,13 @@ def krond(a, b) -> int:
|
|
|
79
79
|
return 1 if a == b else 0
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def heavyside(x) -> int:
|
|
83
|
+
"""
|
|
84
|
+
Heavyside step function.
|
|
85
|
+
"""
|
|
86
|
+
return 1 if x >= 0 else 0
|
|
87
|
+
|
|
88
|
+
|
|
82
89
|
def roundCents(values, decimals=2):
|
|
83
90
|
"""
|
|
84
91
|
Round values in NumPy array down to second decimal.
|
owlplanner/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2025.03.
|
|
1
|
+
__version__ = "2025.03.14"
|
|
@@ -2,16 +2,16 @@ owlplanner/__init__.py,sha256=QqrdT0Qks20osBTg7h0vJHAxpP9lL7DA99xb0nYbtw4,254
|
|
|
2
2
|
owlplanner/abcapi.py,sha256=LbzW_KcNy0IeHp42MUHwGu_H67B2h_e1_vu-c2ACTkQ,6646
|
|
3
3
|
owlplanner/config.py,sha256=uJ6jZ9Rx2CB2P5cFRscsUCXOW6uml7I4pta2ozjW0uo,12263
|
|
4
4
|
owlplanner/logging.py,sha256=tYMw04O-XYSzjTj36fmKJGLcE1VkK6k6oJNeqtKXzuc,2530
|
|
5
|
-
owlplanner/plan.py,sha256=
|
|
5
|
+
owlplanner/plan.py,sha256=2uS_N121p-fPhCboVSAthdt_uVw109ltLTvCLeGouuo,116263
|
|
6
6
|
owlplanner/progress.py,sha256=8jlCvvtgDI89zXVNMBg1-lnEyhpPvKQS2X5oAIpoOVQ,384
|
|
7
7
|
owlplanner/rates.py,sha256=TN407qU4n-bac1oymkQ_n2QKEPwFQxy6JZVGwgIkLQU,15585
|
|
8
8
|
owlplanner/tax2025.py,sha256=B-A5eU3wxdcAaxRCbT3qI-JEKoD_ZeNbg_86XhNdQEI,7745
|
|
9
9
|
owlplanner/timelists.py,sha256=tYieZU67FT6TCcQQis36JaXGI7dT6NqD7RvdEjgJL4M,4026
|
|
10
|
-
owlplanner/utils.py,sha256=
|
|
11
|
-
owlplanner/version.py,sha256=
|
|
10
|
+
owlplanner/utils.py,sha256=WpJgn79YZfH8UCkcmhd-AZlxlGuz1i1-UDBRXImsY6I,2485
|
|
11
|
+
owlplanner/version.py,sha256=gPOv802nGWM84uQsfLVWHJKlgbpuozzBAjyW-OgDkzE,28
|
|
12
12
|
owlplanner/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
owlplanner/data/rates.csv,sha256=6fxg56BVVORrj9wJlUGFdGXKvOX5r7CSca8uhUbbuIU,3734
|
|
14
|
-
owlplanner-2025.3.
|
|
15
|
-
owlplanner-2025.3.
|
|
16
|
-
owlplanner-2025.3.
|
|
17
|
-
owlplanner-2025.3.
|
|
14
|
+
owlplanner-2025.3.14.dist-info/METADATA,sha256=C5BlpAPu2CwtVQmuBUPDTYi7mXg86T3b0dYR5anPUak,53801
|
|
15
|
+
owlplanner-2025.3.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
owlplanner-2025.3.14.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
17
|
+
owlplanner-2025.3.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|