-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from EconForge/albop/docs
Albop/docs
- Loading branch information
Showing
7 changed files
with
359 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ on: | |
push: | ||
branches: | ||
- master | ||
|
||
jobs: | ||
build: | ||
name: Deploy docs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Dolo | ||
|
||
|
||
## Introduction | ||
|
||
Dolo is a tool to describe and solve economic models. It provides a simple classification scheme to describe many types of models, allows to write the models as simple text files and compiles these files into efficient Python objects representing them. It also provides many reference solution algorithms to find the solution of these models under rational expectations. | ||
|
||
Dolo understand several types of nonlinear models with occasionnally binding constraints (with or without exogenous discrete shocks), as well as local pertubations models, like Dynare. It is a very adequate tool to study zero-lower bound issues, or sudden-stop problems, for instance. | ||
|
||
Sophisticated solution routines are available: local perturbations up to third order, perfect foresight solution, policy iteration, value iteration. Most of these solutions are either parallelized or vectorized. They are written in pure Python, and can easily be inspected or adapted. | ||
|
||
Thanks to the simple and consistent Python API for models, it is possible to write models in pure Python, or to implement other solution algorithms on top it. | ||
|
||
|
||
## Frequently Asked Questions | ||
|
||
No question was ever asked. Certainly because it's all very clear. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
## Basic installation | ||
|
||
Dolo can be installed in several ways: | ||
|
||
- with anaconda (recommended): | ||
|
||
`conda install -c conda-forge dolo` | ||
|
||
- with pip | ||
|
||
`pip install dolo` | ||
|
||
|
||
## Developper's installation | ||
|
||
Dolo uses `poetry` as package manager, so you probably need to install poetry before you start developing the package. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,292 @@ | ||
### The following originates from https://github.com/coady/multimethod | ||
|
||
# Copyright 2020 Aric Coady | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
### | ||
|
||
import abc | ||
import collections | ||
import functools | ||
import inspect | ||
import itertools | ||
import types | ||
import typing | ||
from typing import Callable, Iterable, Iterator, Mapping | ||
|
||
__version__ = '1.3' | ||
|
||
|
||
def groupby(func: Callable, values: Iterable) -> dict: | ||
"""Return mapping of key function to values.""" | ||
groups = collections.defaultdict(list) # type: dict | ||
for value in values: | ||
groups[func(value)].append(value) | ||
return groups | ||
|
||
|
||
def get_types(func: Callable) -> tuple: | ||
"""Return evaluated type hints in order.""" | ||
if not hasattr(func, '__annotations__'): | ||
return () | ||
annotations = dict(typing.get_type_hints(func)) | ||
annotations.pop('return', None) | ||
params = inspect.signature(func).parameters | ||
return tuple(annotations.pop(name, object) for name in params if annotations) | ||
|
||
|
||
class DispatchError(TypeError): | ||
pass | ||
|
||
|
||
class subtype(type): | ||
"""A normalized generic type which checks subscripts.""" | ||
|
||
def __new__(cls, tp, *args): | ||
if tp is typing.Any: | ||
return object | ||
if isinstance(tp, typing.TypeVar): | ||
if not tp.__constraints__: | ||
return object | ||
tp = typing.Union[tp.__constraints__] | ||
origin = getattr(tp, '__extra__', getattr(tp, '__origin__', tp)) | ||
args = tuple(map(cls, getattr(tp, '__args__', None) or args)) | ||
if set(args) <= {object} and not (origin is tuple and args): | ||
return origin | ||
bases = (origin,) if type(origin) is type else () | ||
namespace = {'__origin__': origin, '__args__': args} | ||
return type.__new__(cls, str(tp), bases, namespace) | ||
|
||
def __init__(self, tp, *args): | ||
if isinstance(self.__origin__, abc.ABCMeta): | ||
self.__origin__.register(self) | ||
|
||
def __getstate__(self): | ||
return self.__origin__, self.__args__ | ||
|
||
def __eq__(self, other): | ||
return isinstance(other, subtype) and self.__getstate__() == other.__getstate__() | ||
|
||
def __hash__(self): | ||
return hash(self.__getstate__()) | ||
|
||
def __subclasscheck__(self, subclass): | ||
origin = getattr(subclass, '__extra__', getattr(subclass, '__origin__', subclass)) | ||
args = getattr(subclass, '__args__', ()) | ||
if origin is typing.Union: | ||
return all(issubclass(cls, self) for cls in args) | ||
if self.__origin__ is typing.Union: | ||
return issubclass(subclass, self.__args__) | ||
return ( # check args first to avoid a recursion error in ABCMeta | ||
len(args) == len(self.__args__) | ||
and issubclass(origin, self.__origin__) | ||
and all(map(issubclass, args, self.__args__)) | ||
) | ||
|
||
|
||
class signature(tuple): | ||
"""A tuple of types that supports partial ordering.""" | ||
|
||
parents = None # type: set | ||
|
||
def __new__(cls, types: Iterable): | ||
return tuple.__new__(cls, map(subtype, types)) | ||
|
||
def __le__(self, other) -> bool: | ||
return len(self) <= len(other) and all(map(issubclass, other, self)) | ||
|
||
def __lt__(self, other) -> bool: | ||
return self != other and self <= other | ||
|
||
def __sub__(self, other) -> tuple: | ||
"""Return relative distances, assuming self >= other.""" | ||
mros = (subclass.mro() for subclass in self) | ||
return tuple(mro.index(cls if cls in mro else object) for mro, cls in zip(mros, other)) | ||
|
||
|
||
class multimethod(dict): | ||
"""A callable directed acyclic graph of methods.""" | ||
|
||
pending = None # type: set | ||
|
||
def __new__(cls, func): | ||
namespace = inspect.currentframe().f_back.f_locals | ||
self = functools.update_wrapper(dict.__new__(cls), func) | ||
self.pending = set() | ||
self.get_type = type # default type checker | ||
return namespace.get(func.__name__, self) | ||
|
||
def __init__(self, func: Callable): | ||
try: | ||
self[get_types(func)] = func | ||
except NameError: | ||
self.pending.add(func) | ||
|
||
def register(self, *args): | ||
"""Decorator for registering a function. | ||
Optionally call with types to return a decorator for unannotated functions. | ||
""" | ||
if len(args) == 1 and hasattr(args[0], '__annotations__'): | ||
return overload.register(self, *args) | ||
return lambda func: self.__setitem__(args, func) or func | ||
|
||
def __get__(self, instance, owner): | ||
return self if instance is None else types.MethodType(self, instance) | ||
|
||
def parents(self, types: tuple) -> set: | ||
"""Find immediate parents of potential key.""" | ||
parents = {key for key in self if isinstance(key, signature) and key < types} | ||
return parents - {ancestor for parent in parents for ancestor in parent.parents} | ||
|
||
def clean(self): | ||
"""Empty the cache.""" | ||
for key in list(self): | ||
if not isinstance(key, signature): | ||
super().__delitem__(key) | ||
|
||
def __setitem__(self, types: tuple, func: Callable): | ||
self.clean() | ||
types = signature(types) | ||
parents = types.parents = self.parents(types) | ||
for key in self: | ||
if types < key and (not parents or parents & key.parents): | ||
key.parents -= parents | ||
key.parents.add(types) | ||
if any(isinstance(cls, subtype) for cls in types): | ||
self.get_type = get_type # switch to slower generic type checker | ||
super().__setitem__(types, func) | ||
self.__doc__ = self.docstring | ||
|
||
def __delitem__(self, types: tuple): | ||
self.clean() | ||
super().__delitem__(types) | ||
for key in self: | ||
if types in key.parents: | ||
key.parents = self.parents(key) | ||
self.__doc__ = self.docstring | ||
|
||
def __missing__(self, types: tuple) -> Callable: | ||
"""Find and cache the next applicable method of given types.""" | ||
self.evaluate() | ||
if types in self: | ||
return self[types] | ||
groups = groupby(signature(types).__sub__, self.parents(types)) | ||
keys = groups[min(groups)] if groups else [] | ||
funcs = {self[key] for key in keys} | ||
if len(funcs) == 1: | ||
return self.setdefault(types, *funcs) | ||
msg = f"{self.__name__}: {len(keys)} methods found" # type: ignore | ||
raise DispatchError(msg, types, keys) | ||
|
||
def __call__(self, *args, **kwargs): | ||
"""Resolve and dispatch to best method.""" | ||
return self[tuple(map(self.get_type, args))](*args, **kwargs) | ||
|
||
def evaluate(self): | ||
"""Evaluate any pending forward references. | ||
This can be called explicitly when using forward references, | ||
otherwise cache misses will evaluate. | ||
""" | ||
while self.pending: | ||
func = self.pending.pop() | ||
self[get_types(func)] = func | ||
|
||
@property | ||
def docstring(self): | ||
"""a descriptive docstring of all registered functions""" | ||
docs = [] | ||
for func in set(self.values()): | ||
try: | ||
sig = inspect.signature(func) | ||
except ValueError: | ||
sig = '' | ||
doc = func.__doc__ or '' | ||
docs.append(f'{func.__name__}{sig}\n {doc}') | ||
return '\n\n'.join(docs) | ||
|
||
|
||
class multidispatch(multimethod): | ||
"""Provisional wrapper for future compatibility with `functools.singledispatch`.""" | ||
|
||
|
||
get_type = multimethod(type) | ||
get_type.__doc__ = """Return a generic `subtype` which checks subscripts.""" | ||
for atomic in (Iterator, str, bytes): | ||
get_type[atomic,] = type | ||
|
||
|
||
@multimethod # type: ignore[no-redef] | ||
def get_type(arg: tuple): | ||
"""Return generic type checking all values.""" | ||
return subtype(type(arg), *map(get_type, arg)) | ||
|
||
|
||
@multimethod # type: ignore[no-redef] | ||
def get_type(arg: Mapping): | ||
"""Return generic type checking first item.""" | ||
return subtype(type(arg), *map(get_type, next(iter(arg.items()), ()))) | ||
|
||
|
||
@multimethod # type: ignore[no-redef] | ||
def get_type(arg: Iterable): | ||
"""Return generic type checking first value.""" | ||
return subtype(type(arg), *map(get_type, itertools.islice(arg, 1))) | ||
|
||
|
||
def isa(*types) -> Callable: | ||
"""Partially bound `isinstance`.""" | ||
return lambda arg: isinstance(arg, types) | ||
|
||
|
||
class overload(collections.OrderedDict): | ||
"""Ordered functions which dispatch based on their annotated predicates.""" | ||
|
||
__get__ = multimethod.__get__ | ||
|
||
def __new__(cls, func): | ||
namespace = inspect.currentframe().f_back.f_locals | ||
self = functools.update_wrapper(super().__new__(cls), func) | ||
return namespace.get(func.__name__, self) | ||
|
||
def __init__(self, func: Callable): | ||
self[inspect.signature(func)] = func | ||
|
||
def __call__(self, *args, **kwargs): | ||
"""Dispatch to first matching function.""" | ||
for sig, func in reversed(self.items()): | ||
arguments = sig.bind(*args, **kwargs).arguments | ||
if all(predicate( | ||
arguments[name]) for name, predicate in func.__annotations__.items()): | ||
return func(*args, **kwargs) | ||
raise DispatchError("No matching functions found") | ||
|
||
def register(self, func: Callable) -> Callable: | ||
"""Decorator for registering a function.""" | ||
self.__init__(func) # type: ignore | ||
return self if self.__name__ == func.__name__ else func # type: ignore | ||
|
||
|
||
class multimeta(type): | ||
"""Convert all callables in namespace to multimethods.""" | ||
|
||
class __prepare__(dict): | ||
def __init__(*args): | ||
pass | ||
|
||
def __setitem__(self, key, value): | ||
if callable(value): | ||
value = getattr(self.get(key), 'register', multimethod)(value) | ||
super().__setitem__(key, value) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
site_name: Dolo | ||
|
||
theme: | ||
name: mkdocs | ||
|
||
nav: | ||
- Home: index.md | ||
- Install: installation.md | ||
|
||
|
||
markdown_extensions: | ||
- mdx_math | ||
- markdown.extensions.admonition | ||
- pymdownx.details | ||
- pymdownx.arithmatex | ||
- pymdownx.highlight: | ||
use_pygments: true | ||
extend_pygments_lang: [yaml] | ||
- pymdownx.superfences | ||
|
||
extra_css: | ||
- css/extra.css | ||
|
||
extra_javascript: | ||
# other extra java script | ||
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML | ||
- https://cdn.jsdelivr.net/npm/vega@5 | ||
- https://cdn.jsdelivr.net/npm/vega-lite@3 | ||
- https://cdn.jsdelivr.net/npm/vega-embed@5 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters