owlplanner 2025.3.16__py3-none-any.whl → 2025.3.27__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 CHANGED
@@ -307,6 +307,9 @@ class Plan(object):
307
307
  # Previous 2 years for Medicare.
308
308
  self.prevMAGI = np.zeros((2))
309
309
 
310
+ # Default slack on profile.
311
+ self.lambdha = 0
312
+
310
313
  # Scenario starts at the beginning of this year and ends at the end of the last year.
311
314
  s = ["", "s"][self.N_i - 1]
312
315
  self.mylog.vprint(f"Preparing scenario of {self.N_n} years for {self.N_i} individual{s}.")
@@ -1071,6 +1074,9 @@ class Plan(object):
1071
1074
  Cx = self.C["x"]
1072
1075
  Cz = self.C["z"]
1073
1076
 
1077
+ spLo = 1 - self.lambdha
1078
+ spHi = 1 + self.lambdha
1079
+
1074
1080
  tau_ijn = np.zeros((Ni, Nj, Nn))
1075
1081
  for i in range(Ni):
1076
1082
  for j in range(Nj):
@@ -1191,7 +1197,7 @@ class Plan(object):
1191
1197
  # Account for time elapsed in the current year.
1192
1198
  spending *= units * self.yearFracLeft
1193
1199
  # self.mylog.vprint('Maximizing bequest with desired net spending of:', u.d(spending))
1194
- A.addNewRow({_q1(Cg, 0): 1}, spending, spending)
1200
+ A.addNewRow({_q1(Cg, 0): 1}, spLo * spending, spHi * spending)
1195
1201
 
1196
1202
  # Set initial balances through constraints.
1197
1203
  for i in range(Ni):
@@ -1297,8 +1303,10 @@ class Plan(object):
1297
1303
 
1298
1304
  # Impose income profile.
1299
1305
  for n in range(1, Nn):
1300
- rowDic = {_q1(Cg, 0, Nn): -self.xiBar_n[n], _q1(Cg, n, Nn): self.xiBar_n[0]}
1301
- A.addNewRow(rowDic, zero, zero)
1306
+ rowDic = {_q1(Cg, 0, Nn): -spLo * self.xiBar_n[n], _q1(Cg, n, Nn): self.xiBar_n[0]}
1307
+ A.addNewRow(rowDic, zero, inf)
1308
+ rowDic = {_q1(Cg, 0, Nn): spHi * self.xiBar_n[n], _q1(Cg, n, Nn): -self.xiBar_n[0]}
1309
+ A.addNewRow(rowDic, zero, inf)
1302
1310
 
1303
1311
  # Taxable ordinary income.
1304
1312
  for n in range(Nn):
@@ -1362,7 +1370,9 @@ class Plan(object):
1362
1370
  # Now build a solver-neutral objective vector.
1363
1371
  c = abc.Objective(self.nvars)
1364
1372
  if objective == "maxSpending":
1365
- c.setElem(_q1(Cg, 0, Nn), -1)
1373
+ # c.setElem(_q1(Cg, 0, Nn), -1)
1374
+ for n in range(Nn):
1375
+ c.setElem(_q1(Cg, n, Nn), -1/self.gamma_n[n])
1366
1376
  elif objective == "maxBequest":
1367
1377
  for i in range(Ni):
1368
1378
  c.setElem(_q3(Cb, i, 0, Nn, Ni, Nj, Nn + 1), -1)
@@ -1608,6 +1618,7 @@ class Plan(object):
1608
1618
  "units",
1609
1619
  "maxRothConversion",
1610
1620
  "netSpending",
1621
+ "spendingSlack",
1611
1622
  "bequest",
1612
1623
  "bigM",
1613
1624
  "noRothConversions",
@@ -1642,6 +1653,7 @@ class Plan(object):
1642
1653
  if objective == "maxSpending" and "bequest" not in myoptions:
1643
1654
  self.mylog.vprint("Using bequest of $1.")
1644
1655
 
1656
+ self.prevMAGI = np.zeros(2)
1645
1657
  if "previousMAGIs" in myoptions:
1646
1658
  magi = myoptions["previousMAGIs"]
1647
1659
  if len(magi) != 2:
@@ -1653,6 +1665,13 @@ class Plan(object):
1653
1665
  units = 1000
1654
1666
  self.prevMAGI = units * np.array(magi)
1655
1667
 
1668
+ self.lambdha = 0
1669
+ if "spendingSlack" in myoptions:
1670
+ lambdha = myoptions["spendingSlack"]
1671
+ if lambdha < 0 or lambdha > 50:
1672
+ raise ValueError(f"Slack value out of range {lambdha}.")
1673
+ self.lambdha = lambdha / 100
1674
+
1656
1675
  self._adjustParameters()
1657
1676
 
1658
1677
  if "solver" in options:
owlplanner/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2025.03.16"
1
+ __version__ = "2025.03.27"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: owlplanner
3
- Version: 2025.3.16
3
+ Version: 2025.3.27
4
4
  Summary: Owl: Retirement planner with great wisdom
5
5
  Project-URL: HomePage, https://github.com/mdlacasse/owl
6
6
  Project-URL: Repository, https://github.com/mdlacasse/owl
@@ -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=SWTqNMC0Mk2BUO3CBeeyBaz_cCbLddlSgPJaIj2Cc-I,116090
5
+ owlplanner/plan.py,sha256=F243hFv85IIanKtnyjgbGJ1W0Mz8uBBeHBBwW5b9u0k,116830
6
6
  owlplanner/progress.py,sha256=8jlCvvtgDI89zXVNMBg1-lnEyhpPvKQS2X5oAIpoOVQ,384
7
7
  owlplanner/rates.py,sha256=gJaoe-gJqWCQV5qVLlHp-Yn9TSJs-PJzeTbOwMCbqWs,15682
8
8
  owlplanner/tax2025.py,sha256=B-A5eU3wxdcAaxRCbT3qI-JEKoD_ZeNbg_86XhNdQEI,7745
9
9
  owlplanner/timelists.py,sha256=tYieZU67FT6TCcQQis36JaXGI7dT6NqD7RvdEjgJL4M,4026
10
10
  owlplanner/utils.py,sha256=WpJgn79YZfH8UCkcmhd-AZlxlGuz1i1-UDBRXImsY6I,2485
11
- owlplanner/version.py,sha256=Jcl8LadUqoYB6SO4YGpB9C3ZbDxgmTQBNGqCWqKkUzE,28
11
+ owlplanner/version.py,sha256=eT4mV245NLOvKScOuf87I7NOI5wxmOs9MVCyrVUkZeQ,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.16.dist-info/METADATA,sha256=C1iW41089-MQsqEn1cp1-L7kTcwQWnSKGgGOLiZdt-g,53801
15
- owlplanner-2025.3.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- owlplanner-2025.3.16.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
17
- owlplanner-2025.3.16.dist-info/RECORD,,
14
+ owlplanner-2025.3.27.dist-info/METADATA,sha256=hBZoDMbIKBNeQ2vnRLzDovLileZGC05aTXDzo4BxJ04,53801
15
+ owlplanner-2025.3.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ owlplanner-2025.3.27.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
17
+ owlplanner-2025.3.27.dist-info/RECORD,,