Skip to content

HARK 2.0 pre-alpha/code for BST notebook and dashboard#1055

Open
MridulS wants to merge 179 commits intomainfrom
Update-0p11-for-QE-revised-remove-sphinx
Open

HARK 2.0 pre-alpha/code for BST notebook and dashboard#1055
MridulS wants to merge 179 commits intomainfrom
Update-0p11-for-QE-revised-remove-sphinx

Conversation

@MridulS
Copy link
Member

@MridulS MridulS commented Aug 5, 2021

I went in a cleaned up a bit more from the Update-0p11-for-QE-revised branch and removed the boilerplate code files.
This one only has the changes made to the models.

continued from #1050

Didn't want to make changes to the active branches so created a new branch and the branch for this code is in Update-0p11-for-QE-revised-remove-sphinx.

@llorracc
Copy link
Collaborator

llorracc commented Aug 5, 2021 via email

@sbenthall
Copy link
Contributor

These were the changes I made before I merged #1048
bc2051d

This was in accord with my review.
#1048 (comment)

I gathered merging this PR was urgent.

@llorracc
Copy link
Collaborator

llorracc commented Aug 5, 2021

Just tested it and this works with the public version of the BST notebook. Yay!

# vector of point masses; here it is called the pmv (probability mass vector)


class DiscreteDistribution(DiscreteDistributionOld):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be addressed #1051

pseudo = (agent.pseudo_terminal is True)
if not pseudo: # Then it's a real solution; should be part of the list
solution.insert(0, deepcopy(agent.solution_terminal))
completed_cycles = 0 # NOQA
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature of being able to break off and restart a solution maybe can be split out and brought into HARK 1.0

if not go: # Finished solving
# All models should incorporate 'stge_kind', but some have not
# Handle cases where that has not yet been implemented:
if not hasattr(solution_now.Bilt, 'stge_kind'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is "Bilt"?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a deliberate misspelling of "Built" - results that are constructed or might be modified in the course of the solution (as distinct from "Parm" which is parameters fed into the code).

PS. I know your preference is for words that are deliberately spelled correctly. A virtue of my choice of variable names is that if we can agree on a better alternative, a global search and replace will break neither normal text nor code.

to be tested. Must have dict 'conditions' [name]

quiet : boolean
If True, construct the conditions but print nothing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why there needs to be both a verbosity and a quietness setting.

# 1. use it pervasively
# * in which case there should be a companion set_parameter
# or
# 2. Eliminate it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point.
I believe the issue is that the parameters dictionary was implemented for a few good reasons, but for backwards compatibility, it was made consistent with the 'whiteboard' way of accessing parameters through object attributes.

# 'approximation' pars
self.aprox_par = {'tolerance': self.tolerance, 'seed': self.seed}
# if lim exists, sol approaches truth as all aprox_par -> lim
self.aprox_lim = {'tolerance': 0.0, 'seed': None}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From logging.DEBUG to logging.CRITICAL

quietly : boolean
If True, suppress output
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again.. if you want it to be quiet, couldn't you set the messaging level to something minimal?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some circs in which you may want things to run quietly, but not disturb the messaging level set by the user. It would be a pain (and error-prone) to set self.messaging_level_originally_requested_by_user then set the logging level to suppress all output, then to restore self.messaging_level_originally_requested_by_user. There are a couple of other use cases as well, like you might want to suppress output while debugging because you know what it will be and it just clutters things up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a pain for who? error prone for who?
it seems natural for the method-level logging level to be scoped to the particular method, while leaving a global logging level in place as the default.

@sbenthall
Copy link
Contributor

@llorracc you mention some handling of EOP shocks in this comment: #1039 (comment)

I've been looking for the corresponding changes in this PR but haven't been able to find them amid all the other changes.

I wonder if you could point to them directly.

'chosen_to_next_BOP': {}, # or
'chosen_to_next_choice': {},
'EOP_to_next_BOP': {}, # or
'EOP_to_next_choice': {}, # EOP: End of Problem/Period
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support for BOP and EOP here:
#1039 (comment)

class Prospectations(SimpleNamespace):
"""Expectations prior to the realization of current period shocks."""

pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the ambiguity about the timing of the shocks... maybe it would be better to have these namespaces be numbered, or indexed by the name of some variable, rather than named

'BOP_to_choice': BOP_to_choice
}

return possible_transitions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a lot of structure is implied by the code here.
I wonder if it would be possible to require that the equations get laid out, and then have the structure (such as the timing of the shocks) be inferred from the model specification.
That's more along the lines of what I'm working on in #865

_log.info('\t' + str(Tran[key]['raw_text'][eqn_name]))

def check_conditions(self, messaging_level=logging.DEBUG, quietly=False):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the check_conditions functionality is properly part of the solver functionality or part of the type defintion


Bilt.__dict__.update({k: v for k, v in Modl.Rewards.vals.items()})

return soln
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused why a utility function (which knows its derivatives, which is an awesome addition!) returns a solution object.

Seems like a mathematical function is more basic than a solution.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was part of a pattern of building up the contents of the solution object with standalone commands like

soln = self.def_utility_CRRA()
soln = self.def_value_funcs()

Probably would be an improvement to have self.def_utility_CRRA() return a 'utility' object, def_value_funcs a vFunc, etc. Then the calling code could become:

soln.u = self.def_utility_CRRA()
soln.vFunc = self.def_value_funcs()

The way these kinds of things are done right now is rather haphazard and inconsistent. An improvement in the code would be to systematize and regularlize these kinds of things.

init_perfect_foresight_plus.update( # In principle, kinks exist all the way to infinity
{'aprox_par': {'MaxKinks': '500'}})
init_perfect_foresight_plus.update( # In principle, kinks exist all the way to infinity
{'aprox_lim': {'MaxKinks': float('inf')}})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #914

CRRA_fcts.update({'prmtv_par': 'True'})
init_perfect_foresight_plus['prmtv_par'].append('CRRA')
# init_perfect_foresight_plus['_fcts'].update({'CRRA': CRRA_fcts})
init_perfect_foresight_plus.update({'CRRA_fcts': CRRA_fcts})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you remind me what the different fcts are for?
Some seem to be about presentation.

Maybe a more explicit presentation layer is in order.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a better alternative would be to use a python feature introduced in 3.9: Variables can now have documentation/explanations directly attached to them. Basically, seems to allow what I'm doing by adding _fcts, as built-in part of python.

I've long wondered why programming languages don't all allow this, rather than expecting users to hunt down potential explanations in the code. No good reason, I think.

init_perfect_foresight_plus.update({'T_age_fcts': T_age_fcts})

T_cycle_fcts = {
'about': 'Number of periods in a "cycle" (like, a lifetime) for this agent type'}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this can be inferred from the documentation

As all aprox parameters approach their limits simultaneously,
the numerical solution should converge to the 'true' solution
that would be obtained with infinite computational power

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'permShk': shks_permuted[permPos] + zeros, # + zeros fixes size
'tranShk': shks_permuted[tranPos] + zeros, # all pmts for each st
'aNrm': starting_states
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could the names of variables be extracted out for more generalizability?

@sbenthall
Copy link
Contributor

This PR will never be merged into HARK, though it has been used as a source of inspiration to more recent work.
Can it be closed (to take it off the active PR's list)?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request represents a major refactoring effort for HARK 2.0, continuing from PR #1050. It restructures the ConsIndShockModel by splitting it into multiple focused files and adds extensive enhancements for logging, condition checking, and solver functionality.

Changes:

  • Reorganized ConsIndShockModel.py into separate modules (AgentTypes, AgentDicts, AgentSolve, Both, KinkedRSolver)
  • Added comprehensive logging infrastructure to core.py
  • Enhanced solver with resumption capability and improved progress reporting
  • Refactored DiscreteDistribution to add pmv (probability mass vector) attribute
  • Expanded LaTeX preamble for notebook rendering
  • Added BufferStockTheory dolo model specification

Reviewed changes

Copilot reviewed 12 out of 19 changed files in this pull request and generated 48 comments.

Show a summary per file
File Description
requirements.txt Added trailing empty line
examples/ConsumptionSaving/example_ConsPortfolioModel.ipynb Cleaned up empty cells, updated Python version
HARK/utilities.py Whitespace cleanup and extensive LaTeX macro additions
HARK/distribution.py Refactored DiscreteDistribution with pmv attribute
HARK/core.py Major additions: logging, condition checking, solver enhancements
HARK/ConsumptionSaving/tests/test_IndShockConsumerType.py Trailing whitespace fix
HARK/ConsumptionSaving/dolo_models/IndShockConsumerType.yaml New dolo model specification
HARK/ConsumptionSaving/ConsRiskyContribModel.py Added trailing newline
HARK/ConsumptionSaving/ConsIndShockModel_KinkedRSolver.py New file with kinked R solver implementation
HARK/ConsumptionSaving/ConsIndShockModel_Both.py New file with shared utility functions
HARK/ConsumptionSaving/ConsIndShockModel_AgentTypes.py New file with agent type definitions
HARK/ConsumptionSaving/ConsIndShockModel_AgentDicts.py New file with parameter dictionaries
HARK/ConsumptionSaving/ConsIndShockModel.py Refactored to import from new modules
Documentation/reference/index.rst Added trailing newline
Documentation/reference/ConsumptionSaving/index.rst Added trailing newline
Comments suppressed due to low confidence (1)

HARK/ConsumptionSaving/ConsRiskyContribModel.py:113

  • This method requires 1 positional argument, whereas overridden AgentType.pre_solve requires 2.
    def pre_solve(self):

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +55 to +94
def core_check_condition(name, test, messages, messaging_level, verbose_messages, fact, stage_solution, quietly=False):
"""
Checks whether parameter values of a model satisfy a condition

Parameters
----------
name : string
Name for the condition.

test : function(self -> boolean)
A function (of self) which tests the condition

verbose : Boolean
If False, print minimal information about the condition and exit
If True, print more detailed information and a reference

messages : dict{boolean : string}
A dictionary with boolean keys containing values
for messages to print if the condition is
true or false.

messaging_level : int
Controls verbosity of messages. logging.DEBUG is most verbose,
logging.INFO is less verbose, logging.WARNING speaks up only if the
model might have problems, logging.CRITICAL indicates it is degenerate.

verbose_messages : dict{boolean : string}
(Optional) A dictionary with boolean keys containing supplemental
messages to print if the condition is
true or false if messaging_level is logging.DEBUG

fact : string
Name of the fact (for recording the results)

stage_solution : solution for a stage of the problem containing a condition
to be tested. Must have dict 'conditions' [name]

quiet : boolean
If True, construct the conditions but print nothing
"""
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core_check_condition function has a parameter mismatch in its signature and usage. The signature on line 55 includes a quietly parameter, but the docstring on line 92 documents it as quiet. This inconsistency should be fixed to use the same name throughout.

Copilot uses AI. Check for mistakes.
if 'iter_status' in solution_next.stge_kind:
if solution_next.stge_kind['iter_status'] == 'finished':
_log.info('The model has already been solved.')
# print('The existing solution solves the problem')
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solve function adds extensive complexity with resumption logic, but there's a potential issue: on line 1084, there's a commented-out print statement that should be removed. Commented-out code should not be committed to the repository as it creates maintenance burden and confusion about whether the code should be active.

Suggested change
# print('The existing solution solves the problem')

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +106
self.bilt.Rboro = self.Rboro = Rboro
self.bilt.Rsave = self.Rsave = Rsave
self.bilt.cnstrct = {'vFuncBool', 'IncShkDstn'}

self.Rboro = Rboro
self.Rsave = Rsave
self.cnstrct = {'vFuncBool', 'IncShkDstn'}

Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name bilt (lowercase) is used on lines 99-100 where it should likely be Bilt (capitalized) to match the convention used throughout the rest of the codebase. The class uses self.bilt and self.Bilt interchangeably which creates confusion. Lines 99-106 use lowercase bilt while this appears to be intended as the same object as the capitalized Bilt used elsewhere in the module.

Copilot uses AI. Check for mistakes.
cFunc = cFunc_terminal_nobequest_

CRRA = 2.0
def u(c): CRRAutility(c, CRRA)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 320 defines a utility function but is missing a return statement. The code def u(c): CRRAutility(c, CRRA) should be def u(c): return CRRAutility(c, CRRA). This is a critical bug that will cause the function to return None instead of the utility value.

Suggested change
def u(c): CRRAutility(c, CRRA)
def u(c): return CRRAutility(c, CRRA)

Copilot uses AI. Check for mistakes.
# Attach the corresponding one-stage solver to the agent
# This is what gets called when the user invokes [instance].solve()
if (solverType == 'HARK') or (solverType == 'DARKolo'):
# breakpoint()
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple commented-out lines (773, 774, 1096) containing breakpoint() calls should be removed. These are debugging statements that should not be committed to the repository.

Suggested change
# breakpoint()

Copilot uses AI. Check for mistakes.
m_init_guess = self.mNrmMinNow + self.Ex_IncNext
try:
mNrmStE = newton(Ex_PermShk_tp1_times_m_tp1_minus_m_t, m_init_guess)
except:
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except block directly handles BaseException.

Suggested change
except:
except Exception:

Copilot uses AI. Check for mistakes.
self.Bilt.mNrmTrg = find_zero_newton(
self.E_Next_.m_tp1_minus_m_t,
m_init_guess)
except:
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except block directly handles BaseException.

Copilot uses AI. Check for mistakes.
try:
self.Bilt.mNrmStE = find_zero_newton(
self.E_Next_.permGroShk_tp1_times_m_tp1_Over_m_t_minus_PGro, m_init_guess)
except:
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except block directly handles BaseException.

Suggested change
except:
except Exception:

Copilot uses AI. Check for mistakes.
# For example, computing stable points for inf hor buffer stock
# Overwritten in PerfForesightConsumerSolution, carrying over
# to IndShockConsumerType
pass
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary 'pass' statement.

Suggested change
pass

Copilot uses AI. Check for mistakes.
Comment on lines +1120 to +1125
from dolo import yaml_import
self.dolo_modl = yaml_import(
'/Volumes/Data/Code/ARK/DARKolo/chimeras/BufferStock/bufferstock.yaml'
)
if self.verbose >= 2:
_log.info(self.dolo_modl)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement is unreachable.

Suggested change
from dolo import yaml_import
self.dolo_modl = yaml_import(
'/Volumes/Data/Code/ARK/DARKolo/chimeras/BufferStock/bufferstock.yaml'
)
if self.verbose >= 2:
_log.info(self.dolo_modl)
# from dolo import yaml_import
# self.dolo_modl = yaml_import(
# '/Volumes/Data/Code/ARK/DARKolo/chimeras/BufferStock/bufferstock.yaml'
# )
# if self.verbose >= 2:
# _log.info(self.dolo_modl)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Stale

Development

Successfully merging this pull request may close these issues.

4 participants