From 758b75067fd5c1d983449b40fe23eadd4353c9e2 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 08:16:56 -0300 Subject: [PATCH 01/17] Uses new plugin API --- economicsl/__init__.py | 4 ++-- setup.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/economicsl/__init__.py b/economicsl/__init__.py index 99af35c..02010e5 100644 --- a/economicsl/__init__.py +++ b/economicsl/__init__.py @@ -12,9 +12,9 @@ class Agent(abce.Agent): def __init__(self, id, group, trade_logging, - database, logger, random_seed, num_managers): + database, random_seed, num_managers, agent_parameters, simulation_parameters): super().__init__(id, group, trade_logging, - database, logger, random_seed, num_managers) + database, random_seed, num_managers, agent_parameters, simulation_parameters) self.name = (group, self.id) self.alive = True self.mainLedger = Ledger(self, self._haves) diff --git a/setup.py b/setup.py index c70f3b4..819a91f 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,4 @@ +"""Installs abcesl, the contracting and accounting package for ABCE""" from setuptools import setup @@ -9,4 +10,5 @@ author_email='rhtbot@protonmail.com', license='MIT', packages=['economicsl'], - zip_safe=False) + zip_safe=False, + install_requires=['abce']) From 2d53431d30b5e378225309321fb47686b5b77757 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Tue, 5 Sep 2017 09:36:28 -0300 Subject: [PATCH 02/17] Puts ABCE into README.md Puts ABCE link into README.md Puts ABCE link into README.md2 --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a84cb50..0d60a57 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ -# python-distilledESL +# ABCESL [![DOI](https://zenodo.org/badge/95235014.svg)](https://zenodo.org/badge/latestdoi/95235014) -[![Build Status](https://travis-ci.org/rht/py-distilledESL.svg?branch=master)](https://travis-ci.org/rht/py-distilledESL) + This is a python port of the entirety of https://github.com/EconomicSL/distilledESL, further trimmed to its essence. -`distilledESL` is a rewrite of Core-ESL, a reference implementation of Economic +It is a plugin to [ABCE](https://github.com/AB-CE/abce); +`ABCESL` is a reference implementation of Economic Simulation Library. I anticipate that the spec for the EconomicSL library will be cross-language, just like in the case of a spec of an IETF RFC. @@ -13,16 +14,21 @@ Currently it has a feature and implementation parity with: https://github.com/EconomicSL/distilledESL/commit/1ef2d0a9fed490990f2a600b2916391de50bdea8 (May 10 2017) The goal of this repo is to become a catalyst for a convergent evolution between the -API designs in Core-ESL / distilled-ESL / ABCE. Ideas can be tested much more +API designs distilled-ESL / ABCE. Ideas can be tested much more easily here. +While the ABCESL is currently used in Oxford for our stress testing model, +it is sparsely documented and lacks behavioral and unit tests. This is in +sharp contrast to ABCE, which has a good documentation and a test coverage +of all essential functions. + ## Installation This library uses Python >=3.5 type system, so it runs only on python>=3.5. To install, run ``` -$ pip install git+https://github.com/rht/py-distilledESL +$ pip install git+https://github.com/AB-CE/ABCESL.git ``` or from this repo From 90ad79e05d8fc41b40ed4621b47dd4cb1f4e8950 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 16:18:28 -0300 Subject: [PATCH 03/17] rebase --- economicsl/__init__.py | 144 +------------------- economicsl/account.py | 39 ++++++ economicsl/accounting.py | 11 +- economicsl/action.py | 41 ++++++ economicsl/agent.py | 83 +++++++++++ economicsl/bankersrounding.py | 14 ++ economicsl/contract.py | 15 ++ economicsl/contracts.py | 15 ++ economicsl/ledger.py | 227 +++++++++++++++++++++++++++++++ economicsl/obligationmessage.py | 16 +++ economicsl/obligations.py | 116 +--------------- economicsl/obligationsmailbox.py | 90 ++++++++++++ 12 files changed, 557 insertions(+), 254 deletions(-) create mode 100644 economicsl/account.py create mode 100644 economicsl/action.py create mode 100644 economicsl/agent.py create mode 100644 economicsl/bankersrounding.py create mode 100644 economicsl/contract.py create mode 100644 economicsl/contracts.py create mode 100644 economicsl/ledger.py create mode 100644 economicsl/obligationmessage.py create mode 100644 economicsl/obligationsmailbox.py diff --git a/economicsl/__init__.py b/economicsl/__init__.py index 02010e5..57fc91b 100644 --- a/economicsl/__init__.py +++ b/economicsl/__init__.py @@ -1,148 +1,14 @@ from typing import List import numpy as np -from .accounting import Ledger +from .ledger import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox from .obligations import Obligation from .accounting import AccountType # NOQA from abce import NotEnoughGoods # NOQA import abce - - -class Agent(abce.Agent): - def __init__(self, id, group, trade_logging, - database, random_seed, num_managers, agent_parameters, simulation_parameters): - super().__init__(id, group, trade_logging, - database, random_seed, num_managers, agent_parameters, simulation_parameters) - self.name = (group, self.id) - self.alive = True - self.mainLedger = Ledger(self, self._haves) - self.obligationsAndGoodsMailbox = ObligationsAndGoodsMailbox(self) - - def init(self, agent_parameters, sim_parameters): - super().init(agent_parameters, sim_parameters) - self.simulation = sim_parameters - - def _begin_subround(self): - super()._begin_subround() - self.step() - - def add(self, contract) -> None: - if (contract.getAssetParty() == self.name): - # This contract is an asset for me. - self.mainLedger.addAsset(contract) - elif (contract.getLiabilityParty() == self.name): - # This contract is a liability for me - self.mainLedger.addLiability(contract) - else: - print(contract, contract.getAssetParty(), contract.getLiabilityParty(), (self.name)) - raise Exception("who the fuck is this" ) - - def getName(self): - return str(self.name) - - def getTime(self): - return self.simulation.time - - def getSimulation(self): - return self.simulation - - def isAlive(self) -> bool: - return self.alive - - def addCash(self, amount: np.longdouble) -> None: - self.mainLedger.inventory.create('money', amount) - - def getCash_(self) -> np.longdouble: - return self.mainLedger.inventory['money'] - - def getMainLedger(self) -> Ledger: - return self.mainLedger - - def step(self) -> None: - self.obligationsAndGoodsMailbox.step() - - def sendObligation(self, recipient, obligation: Obligation) -> None: - if isinstance(obligation, ObligationMessage): - self.message(recipient.group, recipient.id, '!oblmsg', obligation) - self.obligationsAndGoodsMailbox.addToObligationOutbox(obligation) - else: - msg = ObligationMessage(self, obligation) - self.message(recipient.group, recipient.id, '!oblmsg', msg) - - - def printMailbox(self) -> None: - self.obligationsAndGoodsMailbox.printMailbox() - - def message(self, receiver, topic, content, overload=None): - - if not isinstance(receiver, tuple): - receiver = receiver.name - super().message(receiver[0], receiver[1], topic, content) - - - def get_obligation_outbox(self) -> List[Obligation]: - return self.obligationsAndGoodsMailbox.getObligation_outbox() - - -class Action: - def __init__(self, me: Agent) -> None: - self.me = me - self.amount = np.longdouble(0.0) - - def perform(self) -> None: - print("Model.actionsRecorder.recordAction(this); not called because deleted") - - def getAmount(self) -> np.longdouble: - return self.amount - - def setAmount(self, amount: np.longdouble): - self.amount = np.longdouble(amount) - - def getTime(self) -> int: - return self.me.getTime() - - def print(self, actions=None) -> None: - if actions: - counter = 1 - for action in actions: - print("Action", counter, "->", action.getName()) - counter += 1 - - def getAgent(self) -> Agent: - return self.me - - def getSimulation(self): - return self.me.getSimulation() - - -class Contract: - def getAssetParty(self): - pass - - def getLiabilityParty(self): - pass - - def getValue(self, me): - pass - - def getAvailableActions(self, me): - pass - - def getName(self, me): - pass - - -class BankersRounding: - def bankersRounding(self, value: np.longdouble) -> int: - s = int(value) - t = abs(value - s) - - if ((t < 0.5) or (t == 0.5 and s % 2 == 0)): - return s - else: - if (value < 0): - return s - 1 - else: - return s + 1 +from .agent import Agent +from .action import Action +from .contract import Contract +from .bankersrounding import BankersRounding diff --git a/economicsl/account.py b/economicsl/account.py new file mode 100644 index 0000000..1f17178 --- /dev/null +++ b/economicsl/account.py @@ -0,0 +1,39 @@ +class Account: + def __init__(self, name, accountType, startingBalance=0.0) -> None: + self.name = name + self.accountType = accountType + self.balance = np.longdouble(startingBalance) + + # A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. + def debit(self, amount): + if (self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES): + self.balance += amount + else: + self.balance -= amount + + # A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. + def credit(self, amount): + if ((self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES)): + self.balance -= amount + else: + self.balance += amount + + def getAccountType(self): + return self.accountType + + def getBalance(self): + return self.balance + + def getName(self) -> str: + return self.name + + +def enum(**enums): + return type('Enum', (), enums) + + +AccountType = enum(ASSET=1, + LIABILITY=2, + INCOME=4, + EXPENSES=5, + GOOD=6) diff --git a/economicsl/accounting.py b/economicsl/accounting.py index 87ac789..5cc008c 100644 --- a/economicsl/accounting.py +++ b/economicsl/accounting.py @@ -1,11 +1,17 @@ import numpy as np from abce import NotEnoughGoods +from .account import Account +from .ledger import Ledger def doubleEntry(debitAccount, creditAccount, amount: np.longdouble): debitAccount.debit(amount) creditAccount.credit(amount) +class Contracts: + def __init__(self): + self.allAssets = [] + self.allLiabilities = [] class Account: def __init__(self, name, accountType, startingBalance: np.longdouble=0.0) -> None: @@ -273,7 +279,4 @@ def appreciateLiability(self, liability, valueLost): self.liabilityAccounts.get(liability).credit(valueLost) -class Contracts: - def __init__(self): - self.allAssets = [] - self.allLiabilities = [] + diff --git a/economicsl/action.py b/economicsl/action.py new file mode 100644 index 0000000..477016b --- /dev/null +++ b/economicsl/action.py @@ -0,0 +1,41 @@ +from typing import List +import numpy as np + +from .accounting import Ledger +from .obligations import ObligationMessage, ObligationsAndGoodsMailbox + +from .obligations import Obligation +from .accounting import AccountType # NOQA +from abce import NotEnoughGoods # NOQA +import abce +from .agent import Agent + +class Action: + def __init__(self, me: Agent) -> None: + self.me = me + self.amount = np.longdouble(0.0) + + def perform(self) -> None: + print("Model.actionsRecorder.recordAction(this); not called because deleted") + + def getAmount(self) -> np.longdouble: + return self.amount + + def setAmount(self, amount: np.longdouble): + self.amount = np.longdouble(amount) + + def getTime(self) -> int: + return self.me.getTime() + + def print(self, actions=None) -> None: + if actions: + counter = 1 + for action in actions: + print("Action", counter, "->", action.getName()) + counter += 1 + + def getAgent(self) -> Agent: + return self.me + + def getSimulation(self): + return self.me.getSimulation() diff --git a/economicsl/agent.py b/economicsl/agent.py new file mode 100644 index 0000000..001343b --- /dev/null +++ b/economicsl/agent.py @@ -0,0 +1,83 @@ +from typing import List +import numpy as np + +from .accounting import Ledger +from .obligations import ObligationMessage, ObligationsAndGoodsMailbox + +from .obligations import Obligation +from .accounting import AccountType # NOQA +from abce import NotEnoughGoods # NOQA +import abce + + +class Agent(abce.Agent): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.alive = True + self.mainLedger = Ledger(self, self._haves) + self.obligationsAndGoodsMailbox = ObligationsAndGoodsMailbox(self) + + def init(self, agent_parameters, sim_parameters): + super().init(agent_parameters, sim_parameters) + self.simulation = sim_parameters + + def _begin_subround(self): + super()._begin_subround() + self.step() + + def add(self, contract) -> None: + if (contract.getAssetParty() == self.name): + # This contract is an asset for me. + self.mainLedger.addAsset(contract) + elif (contract.getLiabilityParty() == self.name): + # This contract is a liability for me + self.mainLedger.addLiability(contract) + else: + print(contract, contract.getAssetParty(), contract.getLiabilityParty(), (self.name)) + raise Exception("who the fuck is this" ) + + def getName(self): + return str(self.name) + + def getTime(self): + return self.simulation.time + + def getSimulation(self): + return self.simulation + + def isAlive(self) -> bool: + return self.alive + + def addCash(self, amount: np.longdouble) -> None: + self.mainLedger.inventory.create('money', amount) + + def getCash_(self) -> np.longdouble: + return self.mainLedger.inventory['money'] + + def getMainLedger(self) -> Ledger: + return self.mainLedger + + def step(self) -> None: + self.obligationsAndGoodsMailbox.step() + + def sendObligation(self, recipient, obligation: Obligation) -> None: + if isinstance(obligation, ObligationMessage): + self.message(recipient.group, recipient.id, '!oblmsg', obligation) + self.obligationsAndGoodsMailbox.addToObligationOutbox(obligation) + else: + msg = ObligationMessage(self, obligation) + self.message(recipient.group, recipient.id, '!oblmsg', msg) + + + def printMailbox(self) -> None: + self.obligationsAndGoodsMailbox.printMailbox() + + def message(self, receiver, topic, content, overload=None): + + if not isinstance(receiver, tuple): + receiver = receiver.name + super().message(receiver[0], receiver[1], topic, content) + + + def get_obligation_outbox(self) -> List[Obligation]: + return self.obligationsAndGoodsMailbox.getObligation_outbox() diff --git a/economicsl/bankersrounding.py b/economicsl/bankersrounding.py new file mode 100644 index 0000000..4e41d5a --- /dev/null +++ b/economicsl/bankersrounding.py @@ -0,0 +1,14 @@ +import numpy as np + +class BankersRounding: + def bankersRounding(self, value: np.longdouble) -> int: + s = int(value) + t = abs(value - s) + + if ((t < 0.5) or (t == 0.5 and s % 2 == 0)): + return s + else: + if (value < 0): + return s - 1 + else: + return s + 1 diff --git a/economicsl/contract.py b/economicsl/contract.py new file mode 100644 index 0000000..5c14761 --- /dev/null +++ b/economicsl/contract.py @@ -0,0 +1,15 @@ +class Contract: + def getAssetParty(self): + pass + + def getLiabilityParty(self): + pass + + def getValue(self, me): + pass + + def getAvailableActions(self, me): + pass + + def getName(self, me): + pass diff --git a/economicsl/contracts.py b/economicsl/contracts.py new file mode 100644 index 0000000..fe42213 --- /dev/null +++ b/economicsl/contracts.py @@ -0,0 +1,15 @@ +import numpy as np +from abce import NotEnoughGoods +from .account import Account +from ledger import Ledger + +def doubleEntry(debitAccount, creditAccount, amount: np.longdouble): + debitAccount.debit(amount) + creditAccount.credit(amount) + +class Contracts: + def __init__(self): + self.allAssets = [] + self.allLiabilities = [] + + diff --git a/economicsl/ledger.py b/economicsl/ledger.py new file mode 100644 index 0000000..4cb1361 --- /dev/null +++ b/economicsl/ledger.py @@ -0,0 +1,227 @@ + +# This is the main class implementing double entry org.economicsl.accounting. All public operations provided by this class +# are performed as a double entry operation, i.e. a pair of (dr, cr) operations. +# +# A Ledger contains a set of accounts, and is the interface between an agent and its accounts. Agents cannot +# directly interact with accounts other than via a Ledger. +# +# At the moment, a Ledger contains an account for each type of contract, plus an equity account and a cash account. +# +# A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books +# (as in branch banking for example). +class Ledger: + def __init__(self, me, inventory) -> None: + # A StressLedger is a list of accounts (for quicker searching) + + # Each Account includes an inventory to hold one type of contract. + # These hashmaps are used to access the correct account for a given type of contract. + # Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract + # type (such as Loan) can sometimes be an asset and sometimes a liability. + + # A book is initially created with a cash account (it's the simplest possible book) + self.assetAccounts = {} # a hashmap from a contract to a assetAccount + self.inventory = inventory + self.contracts = Contracts() + self.goodsAccounts = {} + self.liabilityAccounts = {} # a hashmap from a contract to a liabilityAccount + self.me = me + + def getAssetValue(self): + return sum([aa.getBalance() for aa in self.assetAccounts.values()]) + + def getLiabilityValue(self): + return sum([la.getBalance() for la in self.liabilityAccounts.values()]) + + def getEquityValue(self): + return self.getAssetValue() - self.getLiabilityValue() + + def getAssetValueOf(self, contractType): + # return assetAccounts.get(contractType).getBalance(); + return sum([c.getValue() for c in self.contracts.allAssets if isinstance(c, contractType)]) + + def getLiabilityValueOf(self, contractType): + # return liabilityAccounts.get(contractType).getBalance(); + return sum([c.getValue() for c in self.contracts.allLiabilities if isinstance(c, contractType)]) + + def getAllAssets(self): + return self.contracts.allAssets + + def getAllLiabilities(self): + return self.contracts.allLiabilities + + def getAssetsOfType(self, contractType): + return [c for c in self.contracts.allAssets if isinstance(c, contractType)] + + def getLiabilitiesOfType(self, contractType): + return [c for c in self.contracts.allLiabilities if isinstance(c, contractType)] + + def addAccount(self, account, contractType): + switch = account.getAccountType() + if switch == AccountType.ASSET: + self.assetAccounts[contractType] = account + elif switch == AccountType.LIABILITY: + self.liabilityAccounts[contractType] = account + + # Not sure what to do with INCOME, EXPENSES + + # Adding an asset means debiting the account relevant to that type of contract + # and crediting equity. + # @param contract an Asset contract to add + def addAsset(self, contract): + assetAccount = self.assetAccounts.get(contract) + + if assetAccount is None: + # If there doesn't exist an Account to hold this type of contract, we create it + assetAccount = Account(contract.getName(self.me), AccountType.ASSET) + self.addAccount(assetAccount, contract) + + assetAccount.debit(contract.getValue()) + + self.contracts.allAssets.append(contract) + + # Adding a liability means debiting equity and crediting the account + # relevant to that type of contract. + # @param contract a Liability contract to add + def addLiability(self, contract): + liabilityAccount = self.liabilityAccounts.get(contract) + + if liabilityAccount is None: + # If there doesn't exist an Account to hold this type of contract, we create it + liabilityAccount = Account(contract.getName(self.me), AccountType.LIABILITY) + self.addAccount(liabilityAccount, contract) + + liabilityAccount.credit(contract.getValue()) + + # Add to the general inventory? + self.contracts.allLiabilities.append(contract) + + def create(self, name, amount, value): + self.inventory.create(name, amount) + physicalthingsaccount = self.getGoodsAccount(name) + physicalthingsaccount.debit(amount * value) + + def destroy(self, name, amount, value=None): + if value is None: + try: + value = self.getPhysicalThingValue(name) + self.destroy(name, amount, value) + except: + raise NotEnoughGoods(name, 0, amount) + else: + self.inventory.destroy(name, amount) + self.getGoodsAccount(name).credit(amount * value) + + def getGoodsAccount(self, name): + account = self.goodsAccounts.get(name) + if account is None: + account = Account(name, AccountType.GOOD) + self.goodsAccounts[name] = account + return account + + def getPhysicalThingValue(self, name): + try: + return self.getGoodsAccount(name).getBalance() / self.inventory.getGood(name) + except: + return 0.0 + + # Reevaluates the current stock of phisical goods at a specified value and books + # the change to org.economicsl.accounting + def revalueGoods(self, name, value): + old_value = self.getGoodsAccount(name).getBalance() + new_value = self.inventory.getGood(name) * value + if (new_value > old_value): + self.getGoodsAccount(name).debit(new_value - old_value) + elif (new_value < old_value): + self.getGoodsAccount(name).credit(old_value - new_value) + + def addCash(self, amount) -> None: + # (dr cash, cr equity) + self.create("money", np.longdouble(amount), 1.0) + + def subtractCash(self, amount) -> None: + self.destroy("money", np.longdouble(amount), 1.0) + + # Operation to pay back a liability loan; debit liability and credit cash + # @param amount amount to pay back + # @param loan the loan which is being paid back + def payLiability(self, amount, loan): + self.liabilityAccount = self.liabilityAccounts.get(loan) + + assert self.inventory.getCash() >= amount # Pre-condition: liquidity has been raised. + + # (dr liability, cr cash ) + doubleEntry(self.liabilityAccount, self['money'], amount) + + # If I've sold an asset, debit cash and credit asset + # @param amount the *value* of the asset + def sellAsset(self, amount, assetType): + assetAccount = self.assetAccounts.get(assetType) + + # (dr cash, cr asset) + doubleEntry(self["money"], assetAccount, amount) + + # Operation to cancel a Loan to someone (i.e. cash in a Loan in the Assets side). + # + # I'm using this for simplicity but note that this is equivalent to selling an asset. + # @param amount the amount of loan that is cancelled + def pullFunding(self, amount, loan): + loanAccount = self.getAccountFromContract(loan) + # (dr cash, cr asset ) + doubleEntry(self.getCashAccount(), loanAccount, amount) + + def printBalanceSheet(self, me): + print("Asset accounts:\n---------------") + for a in self.assetAccounts.values(): + print(a.getName(), "-> %.2f" % a.getBalance()) + + print("Breakdown: ") + for c in self.contracts.allAssets: + print("\t", c.getName(me), " > ", c.getValue()) + print("TOTAL ASSETS: %.2f" % self.getAssetValue()) + + print("\nLiability accounts:\n---------------") + for a in self.liabilityAccounts.values(): + print(a.getName(), " -> %.2f" % a.getBalance()) + for c in self.contracts.allLiabilities: + print("\t", c.getName(me), " > ", c.getValue()) + print("TOTAL LIABILITIES: %.2f" % self.getLiabilityValue()) + print("\nTOTAL EQUITY: %.2f" % self.getEquityValue()) + + print("\nSummary of encumbered collateral:") + # for (Contract contract : getLiabilitiesOfType(Repo.class)) { + # ((Repo) contract).printCollateral(); + # } + print("\n\nTotal cash: ", self.getGoodsAccount("cash").getBalance()) + # print("Encumbered cash: "+me.getEncumberedCash()); + # print("Unencumbered cash: " + (me.getCash_() - me.getEncumberedCash())); + + def getInitialEquity(self): + return self.initialEquity + + def setInitialValues(self): + self.initialEquity = self.getEquityValue() + + def getAccountFromContract(self, contract): + return self.assetAccounts.get(contract) + + def getCashAccount(self): + return self["money"] + + # if an Asset loses value, I must debit equity and credit asset + # @param valueLost the value lost + def devalueAsset(self, asset, valueLost): + self.assetAccounts.get(asset).credit(valueLost) + + # Todo: perform a check here that the Asset account balances match the value of the assets. (?) + + def appreciateAsset(self, asset, valueLost): + self.assetAccounts.get(asset).debit(valueLost) + + def devalueLiability(self, liability, valueLost): + self.liabilityAccounts.get(liability).debit(valueLost) + + def appreciateLiability(self, liability, valueLost): + self.liabilityAccounts.get(liability).credit(valueLost) + + + diff --git a/economicsl/obligationmessage.py b/economicsl/obligationmessage.py new file mode 100644 index 0000000..1821cc7 --- /dev/null +++ b/economicsl/obligationmessage.py @@ -0,0 +1,16 @@ +class ObligationMessage: + def __init__(self, sender, message) -> None: + self.sender = sender + self.message = message + self._is_read = False + + def getSender(self): + return self.sender + + def getMessage(self): + self._is_read = True + return self.message + + def is_read(self) -> bool: + return self._is_read + diff --git a/economicsl/obligations.py b/economicsl/obligations.py index 278d348..ca4b2c8 100644 --- a/economicsl/obligations.py +++ b/economicsl/obligations.py @@ -1,9 +1,9 @@ -import numpy as np - +from .obligationmessage import ObligationMessage +from .obligationsmailbox import ObligationsMailbox as ObligationsAndGoodsMailbox class Obligation: - def __init__(self, contract, amount: np.longdouble, timeLeftToPay: int) -> None: - self.amount = np.longdouble(amount) + def __init__(self, contract, amount, timeLeftToPay: int) -> None: + self.amount = amount self.from_ = contract.getLiabilityParty() self.to = contract.getAssetParty() @@ -21,7 +21,7 @@ def __init__(self, contract, amount: np.longdouble, timeLeftToPay: int) -> None: def fulfil(self): pass - def getAmount(self) -> np.longdouble: + def getAmount(self): return self.amount def isFulfilled(self) -> bool: @@ -58,109 +58,3 @@ def printObligation(self) -> None: self.getTimeToReceive()) -class ObligationMessage: - def __init__(self, sender, message) -> None: - self.sender = sender - self.message = message - self._is_read = False - - def getSender(self): - return self.sender - - def getMessage(self): - self._is_read = True - return self.message - - def is_read(self) -> bool: - return self._is_read - - -class ObligationsAndGoodsMailbox: - def __init__(self, owner) -> None: - self.obligation_unopened = [] - self.obligation_outbox = [] - self.obligation_inbox = [] - self.obligationMessage_unopened = [] - self.obligationMessage_inbox = [] - self.goods_inbox = [] - self.owner = owner - - - def addToObligationOutbox(self, obligation) -> None: - self.obligation_outbox.append(obligation) - - def getMaturedObligations(self) -> np.longdouble: - return sum([o.getAmount() for o in self.obligation_inbox if o.isDue() and not o.isFulfilled()]) - - def getAllPendingObligations(self) -> np.longdouble: - return sum([o.getAmount() for o in self.obligation_inbox if not o.isFulfilled()]) - - def getPendingPaymentsToMe(self) -> np.longdouble: - return sum([o.getAmount() for o in self.obligation_outbox if o.isFulfilled()]) - - def fulfilAllRequests(self) -> None: - for o in self.obligation_inbox: - if not o.isFulfilled(): - o.fulfil() - - def fulfilMaturedRequests(self) -> None: - for o in self.obligation_inbox: - if o.isDue() and not o.isFulfilled(): - o.fulfil() - - def step(self) -> None: - self.obligation_inbox.extend(self.owner.get_messages('!oblmsg')) - # Remove all fulfilled requests - self.obligation_inbox = [o for o in self.obligation_inbox if not o.isFulfilled()] - self.obligation_outbox = [o for o in self.obligation_outbox if not o.isFulfilled()] - - # Remove all requests from agents who have defaulted. - # TODO should be in model not in the library - self.obligation_outbox = [o for o in self.obligation_outbox if o.getFrom().isAlive()] - - # Move all messages in the obligation_unopened to the obligation_inbox - self.obligation_inbox += [o for o in self.obligation_unopened if o.hasArrived()] - self.obligation_unopened = [o for o in self.obligation_unopened if not o.hasArrived()] - - # Remove all fulfilled requests - self.obligationMessage_inbox = [o for o in self.obligationMessage_inbox if not o.is_read()] - - # Move all messages in the obligation_unopened to the obligation_inbox - self.obligationMessage_inbox += list(self.obligationMessage_unopened) - self.obligationMessage_unopened = [] - - # Remove all fulfilled requests - assert not self.goods_inbox - - # Move all messages in the obligation_unopened to the obligation_inbox - - def printMailbox(self) -> None: - if ((not self.obligation_unopened) and (not self.obligation_inbox) and - (not self.obligation_outbox)): - print("\nObligationsAndGoodsMailbox is empty.") - else: - print("\nObligationsAndGoodsMailbox contents:") - if not (not self.obligation_unopened): - print("Unopened messages:") - for o in self.obligation_unopened: - o.printObligation() - - if not (not self.obligation_inbox): - print("Inbox:") - for o in self.obligation_inbox: - o.printObligation() - - if not (not self.obligation_outbox): - print("Outbox:") - for o in self.obligation_outbox: - o.printObligation() - print() - - def getMessageInbox(self): - return self.obligationMessage_inbox - - def getObligation_outbox(self): - return self.obligation_outbox - - def getObligation_inbox(self): - return self.obligation_inbox diff --git a/economicsl/obligationsmailbox.py b/economicsl/obligationsmailbox.py new file mode 100644 index 0000000..2071e8f --- /dev/null +++ b/economicsl/obligationsmailbox.py @@ -0,0 +1,90 @@ + +class ObligationsMailbox: + def __init__(self, owner) -> None: + self.obligation_unopened = [] + self.obligation_outbox = [] + self.obligation_inbox = [] + self.obligationMessage_unopened = [] + self.obligationMessage_inbox = [] + self.goods_inbox = [] + self.owner = owner + + + def addToObligationOutbox(self, obligation) -> None: + self.obligation_outbox.append(obligation) + + def getMaturedObligations(self): + return sum([o.getAmount() for o in self.obligation_inbox if o.isDue() and not o.isFulfilled()]) + + def getAllPendingObligations(self): + return sum([o.getAmount() for o in self.obligation_inbox if not o.isFulfilled()]) + + def getPendingPaymentsToMe(self): + return sum([o.getAmount() for o in self.obligation_outbox if o.isFulfilled()]) + + def fulfilAllRequests(self) -> None: + for o in self.obligation_inbox: + if not o.isFulfilled(): + o.fulfil() + + def fulfilMaturedRequests(self) -> None: + for o in self.obligation_inbox: + if o.isDue() and not o.isFulfilled(): + o.fulfil() + + def step(self) -> None: + self.obligation_inbox.extend(self.owner.get_messages('!oblmsg')) + # Remove all fulfilled requests + self.obligation_inbox = [o for o in self.obligation_inbox if not o.isFulfilled()] + self.obligation_outbox = [o for o in self.obligation_outbox if not o.isFulfilled()] + + # Remove all requests from agents who have defaulted. + # TODO should be in model not in the library + self.obligation_outbox = [o for o in self.obligation_outbox if o.getFrom().isAlive()] + + # Move all messages in the obligation_unopened to the obligation_inbox + self.obligation_inbox += [o for o in self.obligation_unopened if o.hasArrived()] + self.obligation_unopened = [o for o in self.obligation_unopened if not o.hasArrived()] + + # Remove all fulfilled requests + self.obligationMessage_inbox = [o for o in self.obligationMessage_inbox if not o.is_read()] + + # Move all messages in the obligation_unopened to the obligation_inbox + self.obligationMessage_inbox += list(self.obligationMessage_unopened) + self.obligationMessage_unopened = [] + + # Remove all fulfilled requests + assert not self.goods_inbox + + # Move all messages in the obligation_unopened to the obligation_inbox + + def printMailbox(self) -> None: + if ((not self.obligation_unopened) and (not self.obligation_inbox) and + (not self.obligation_outbox)): + print("\nObligationsAndGoodsMailbox is empty.") + else: + print("\nObligationsAndGoodsMailbox contents:") + if not (not self.obligation_unopened): + print("Unopened messages:") + for o in self.obligation_unopened: + o.printObligation() + + if not (not self.obligation_inbox): + print("Inbox:") + for o in self.obligation_inbox: + o.printObligation() + + if not (not self.obligation_outbox): + print("Outbox:") + for o in self.obligation_outbox: + o.printObligation() + print() + + def getMessageInbox(self): + return self.obligationMessage_inbox + + def getObligation_outbox(self): + return self.obligation_outbox + + def getObligation_inbox(self): + return self.obligation_inbox From f4db04002a4557a01059d236876f8ce71f4e1b3f Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 07:27:15 -0300 Subject: [PATCH 04/17] Removes np dependency --- economicsl/__init__.py | 2 -- economicsl/account.py | 2 +- economicsl/accounting.py | 27 +++++++++++++-------------- economicsl/action.py | 7 +++---- economicsl/agent.py | 5 ++--- economicsl/bankersrounding.py | 4 +--- economicsl/contracts.py | 3 +-- economicsl/ledger.py | 4 ++-- 8 files changed, 23 insertions(+), 31 deletions(-) diff --git a/economicsl/__init__.py b/economicsl/__init__.py index 57fc91b..f821dd0 100644 --- a/economicsl/__init__.py +++ b/economicsl/__init__.py @@ -1,6 +1,4 @@ from typing import List -import numpy as np - from .ledger import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox diff --git a/economicsl/account.py b/economicsl/account.py index 1f17178..19551c1 100644 --- a/economicsl/account.py +++ b/economicsl/account.py @@ -2,7 +2,7 @@ class Account: def __init__(self, name, accountType, startingBalance=0.0) -> None: self.name = name self.accountType = accountType - self.balance = np.longdouble(startingBalance) + self.balance = startingBalance # A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. def debit(self, amount): diff --git a/economicsl/accounting.py b/economicsl/accounting.py index 5cc008c..c3cc8a7 100644 --- a/economicsl/accounting.py +++ b/economicsl/accounting.py @@ -1,10 +1,9 @@ -import numpy as np from abce import NotEnoughGoods from .account import Account from .ledger import Ledger -def doubleEntry(debitAccount, creditAccount, amount: np.longdouble): +def doubleEntry(debitAccount, creditAccount, amount): debitAccount.debit(amount) creditAccount.credit(amount) @@ -14,20 +13,20 @@ def __init__(self): self.allLiabilities = [] class Account: - def __init__(self, name, accountType, startingBalance: np.longdouble=0.0) -> None: + def __init__(self, name, accountType, startingBalance=0.0) -> None: self.name = name self.accountType = accountType - self.balance = np.longdouble(startingBalance) + self.balance = startingBalance # A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. - def debit(self, amount: np.longdouble): + def debit(self, amount): if (self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES): self.balance += amount else: self.balance -= amount # A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. - def credit(self, amount: np.longdouble): + def credit(self, amount): if ((self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES)): self.balance -= amount else: @@ -36,7 +35,7 @@ def credit(self, amount: np.longdouble): def getAccountType(self): return self.accountType - def getBalance(self) -> np.longdouble: + def getBalance(self): return self.balance def getName(self) -> str: @@ -81,20 +80,20 @@ def __init__(self, me, inventory) -> None: self.liabilityAccounts = {} # a hashmap from a contract to a liabilityAccount self.me = me - def getAssetValue(self) -> np.longdouble: + def getAssetValue(self): return sum([aa.getBalance() for aa in self.assetAccounts.values()]) - def getLiabilityValue(self) -> np.longdouble: + def getLiabilityValue(self): return sum([la.getBalance() for la in self.liabilityAccounts.values()]) - def getEquityValue(self) -> np.longdouble: + def getEquityValue(self): return self.getAssetValue() - self.getLiabilityValue() - def getAssetValueOf(self, contractType) -> np.longdouble: + def getAssetValueOf(self, contractType): # return assetAccounts.get(contractType).getBalance(); return sum([c.getValue() for c in self.contracts.allAssets if isinstance(c, contractType)]) - def getLiabilityValueOf(self, contractType) -> np.longdouble: + def getLiabilityValueOf(self, contractType): # return liabilityAccounts.get(contractType).getBalance(); return sum([c.getValue() for c in self.contracts.allLiabilities if isinstance(c, contractType)]) @@ -189,11 +188,11 @@ def revalueGoods(self, name, value): elif (new_value < old_value): self.getGoodsAccount(name).credit(old_value - new_value) - def addCash(self, amount: np.longdouble) -> None: + def addCash(self, amount) -> None: # (dr cash, cr equity) self.create("money", np.longdouble(amount), 1.0) - def subtractCash(self, amount: np.longdouble) -> None: + def subtractCash(self, amount) -> None: self.destroy("money", np.longdouble(amount), 1.0) # Operation to pay back a liability loan; debit liability and credit cash diff --git a/economicsl/action.py b/economicsl/action.py index 477016b..429b8c6 100644 --- a/economicsl/action.py +++ b/economicsl/action.py @@ -1,5 +1,4 @@ from typing import List -import numpy as np from .accounting import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox @@ -18,11 +17,11 @@ def __init__(self, me: Agent) -> None: def perform(self) -> None: print("Model.actionsRecorder.recordAction(this); not called because deleted") - def getAmount(self) -> np.longdouble: + def getAmount(self): return self.amount - def setAmount(self, amount: np.longdouble): - self.amount = np.longdouble(amount) + def setAmount(self, amount): + self.amount = amount def getTime(self) -> int: return self.me.getTime() diff --git a/economicsl/agent.py b/economicsl/agent.py index 001343b..1e2f298 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -1,5 +1,4 @@ from typing import List -import numpy as np from .accounting import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox @@ -48,10 +47,10 @@ def getSimulation(self): def isAlive(self) -> bool: return self.alive - def addCash(self, amount: np.longdouble) -> None: + def addCash(self, amount) -> None: self.mainLedger.inventory.create('money', amount) - def getCash_(self) -> np.longdouble: + def getCash_(self): return self.mainLedger.inventory['money'] def getMainLedger(self) -> Ledger: diff --git a/economicsl/bankersrounding.py b/economicsl/bankersrounding.py index 4e41d5a..920b167 100644 --- a/economicsl/bankersrounding.py +++ b/economicsl/bankersrounding.py @@ -1,7 +1,5 @@ -import numpy as np - class BankersRounding: - def bankersRounding(self, value: np.longdouble) -> int: + def bankersRounding(self, value) -> int: s = int(value) t = abs(value - s) diff --git a/economicsl/contracts.py b/economicsl/contracts.py index fe42213..7fb1da8 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -1,9 +1,8 @@ -import numpy as np from abce import NotEnoughGoods from .account import Account from ledger import Ledger -def doubleEntry(debitAccount, creditAccount, amount: np.longdouble): +def doubleEntry(debitAccount, creditAccount, amount): debitAccount.debit(amount) creditAccount.credit(amount) diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 4cb1361..6e1cded 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -136,10 +136,10 @@ def revalueGoods(self, name, value): def addCash(self, amount) -> None: # (dr cash, cr equity) - self.create("money", np.longdouble(amount), 1.0) + self.create("money", amount, 1.0) def subtractCash(self, amount) -> None: - self.destroy("money", np.longdouble(amount), 1.0) + self.destroy("money", amount, 1.0) # Operation to pay back a liability loan; debit liability and credit cash # @param amount amount to pay back From e4b563702798a6aeb4c39f81efaaf7ccb8281737 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 07:33:05 -0300 Subject: [PATCH 05/17] Removes unused accounting.py file --- economicsl/accounting.py | 281 --------------------------------------- 1 file changed, 281 deletions(-) delete mode 100644 economicsl/accounting.py diff --git a/economicsl/accounting.py b/economicsl/accounting.py deleted file mode 100644 index c3cc8a7..0000000 --- a/economicsl/accounting.py +++ /dev/null @@ -1,281 +0,0 @@ -from abce import NotEnoughGoods -from .account import Account -from .ledger import Ledger - - -def doubleEntry(debitAccount, creditAccount, amount): - debitAccount.debit(amount) - creditAccount.credit(amount) - -class Contracts: - def __init__(self): - self.allAssets = [] - self.allLiabilities = [] - -class Account: - def __init__(self, name, accountType, startingBalance=0.0) -> None: - self.name = name - self.accountType = accountType - self.balance = startingBalance - - # A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. - def debit(self, amount): - if (self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES): - self.balance += amount - else: - self.balance -= amount - - # A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. - def credit(self, amount): - if ((self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES)): - self.balance -= amount - else: - self.balance += amount - - def getAccountType(self): - return self.accountType - - def getBalance(self): - return self.balance - - def getName(self) -> str: - return self.name - - -def enum(**enums): - return type('Enum', (), enums) - - -AccountType = enum(ASSET=1, - LIABILITY=2, - INCOME=4, - EXPENSES=5, - GOOD=6) - - -# This is the main class implementing double entry org.economicsl.accounting. All public operations provided by this class -# are performed as a double entry operation, i.e. a pair of (dr, cr) operations. -# -# A Ledger contains a set of accounts, and is the interface between an agent and its accounts. Agents cannot -# directly interact with accounts other than via a Ledger. -# -# At the moment, a Ledger contains an account for each type of contract, plus an equity account and a cash account. -# -# A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books -# (as in branch banking for example). -class Ledger: - def __init__(self, me, inventory) -> None: - # A StressLedger is a list of accounts (for quicker searching) - - # Each Account includes an inventory to hold one type of contract. - # These hashmaps are used to access the correct account for a given type of contract. - # Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract - # type (such as Loan) can sometimes be an asset and sometimes a liability. - - # A book is initially created with a cash account (it's the simplest possible book) - self.assetAccounts = {} # a hashmap from a contract to a assetAccount - self.inventory = inventory - self.contracts = Contracts() - self.goodsAccounts = {} - self.liabilityAccounts = {} # a hashmap from a contract to a liabilityAccount - self.me = me - - def getAssetValue(self): - return sum([aa.getBalance() for aa in self.assetAccounts.values()]) - - def getLiabilityValue(self): - return sum([la.getBalance() for la in self.liabilityAccounts.values()]) - - def getEquityValue(self): - return self.getAssetValue() - self.getLiabilityValue() - - def getAssetValueOf(self, contractType): - # return assetAccounts.get(contractType).getBalance(); - return sum([c.getValue() for c in self.contracts.allAssets if isinstance(c, contractType)]) - - def getLiabilityValueOf(self, contractType): - # return liabilityAccounts.get(contractType).getBalance(); - return sum([c.getValue() for c in self.contracts.allLiabilities if isinstance(c, contractType)]) - - def getAllAssets(self): - return self.contracts.allAssets - - def getAllLiabilities(self): - return self.contracts.allLiabilities - - def getAssetsOfType(self, contractType): - return [c for c in self.contracts.allAssets if isinstance(c, contractType)] - - def getLiabilitiesOfType(self, contractType): - return [c for c in self.contracts.allLiabilities if isinstance(c, contractType)] - - def addAccount(self, account, contractType): - switch = account.getAccountType() - if switch == AccountType.ASSET: - self.assetAccounts[contractType] = account - elif switch == AccountType.LIABILITY: - self.liabilityAccounts[contractType] = account - - # Not sure what to do with INCOME, EXPENSES - - # Adding an asset means debiting the account relevant to that type of contract - # and crediting equity. - # @param contract an Asset contract to add - def addAsset(self, contract): - assetAccount = self.assetAccounts.get(contract) - - if assetAccount is None: - # If there doesn't exist an Account to hold this type of contract, we create it - assetAccount = Account(contract.getName(self.me), AccountType.ASSET) - self.addAccount(assetAccount, contract) - - assetAccount.debit(contract.getValue()) - - self.contracts.allAssets.append(contract) - - # Adding a liability means debiting equity and crediting the account - # relevant to that type of contract. - # @param contract a Liability contract to add - def addLiability(self, contract): - liabilityAccount = self.liabilityAccounts.get(contract) - - if liabilityAccount is None: - # If there doesn't exist an Account to hold this type of contract, we create it - liabilityAccount = Account(contract.getName(self.me), AccountType.LIABILITY) - self.addAccount(liabilityAccount, contract) - - liabilityAccount.credit(contract.getValue()) - - # Add to the general inventory? - self.contracts.allLiabilities.append(contract) - - def create(self, name, amount, value): - self.inventory.create(name, amount) - physicalthingsaccount = self.getGoodsAccount(name) - physicalthingsaccount.debit(amount * value) - - def destroy(self, name, amount, value=None): - if value is None: - try: - value = self.getPhysicalThingValue(name) - self.destroy(name, amount, value) - except: - raise NotEnoughGoods(name, 0, amount) - else: - self.inventory.destroy(name, amount) - self.getGoodsAccount(name).credit(amount * value) - - def getGoodsAccount(self, name): - account = self.goodsAccounts.get(name) - if account is None: - account = Account(name, AccountType.GOOD) - self.goodsAccounts[name] = account - return account - - def getPhysicalThingValue(self, name): - try: - return self.getGoodsAccount(name).getBalance() / self.inventory.getGood(name) - except: - return 0.0 - - # Reevaluates the current stock of phisical goods at a specified value and books - # the change to org.economicsl.accounting - def revalueGoods(self, name, value): - old_value = self.getGoodsAccount(name).getBalance() - new_value = self.inventory.getGood(name) * value - if (new_value > old_value): - self.getGoodsAccount(name).debit(new_value - old_value) - elif (new_value < old_value): - self.getGoodsAccount(name).credit(old_value - new_value) - - def addCash(self, amount) -> None: - # (dr cash, cr equity) - self.create("money", np.longdouble(amount), 1.0) - - def subtractCash(self, amount) -> None: - self.destroy("money", np.longdouble(amount), 1.0) - - # Operation to pay back a liability loan; debit liability and credit cash - # @param amount amount to pay back - # @param loan the loan which is being paid back - def payLiability(self, amount, loan): - self.liabilityAccount = self.liabilityAccounts.get(loan) - - assert self.inventory.getCash() >= amount # Pre-condition: liquidity has been raised. - - # (dr liability, cr cash ) - doubleEntry(self.liabilityAccount, self['money'], amount) - - # If I've sold an asset, debit cash and credit asset - # @param amount the *value* of the asset - def sellAsset(self, amount, assetType): - assetAccount = self.assetAccounts.get(assetType) - - # (dr cash, cr asset) - doubleEntry(self["money"], assetAccount, amount) - - # Operation to cancel a Loan to someone (i.e. cash in a Loan in the Assets side). - # - # I'm using this for simplicity but note that this is equivalent to selling an asset. - # @param amount the amount of loan that is cancelled - def pullFunding(self, amount, loan): - loanAccount = self.getAccountFromContract(loan) - # (dr cash, cr asset ) - doubleEntry(self.getCashAccount(), loanAccount, amount) - - def printBalanceSheet(self, me): - print("Asset accounts:\n---------------") - for a in self.assetAccounts.values(): - print(a.getName(), "-> %.2f" % a.getBalance()) - - print("Breakdown: ") - for c in self.contracts.allAssets: - print("\t", c.getName(me), " > ", c.getValue()) - print("TOTAL ASSETS: %.2f" % self.getAssetValue()) - - print("\nLiability accounts:\n---------------") - for a in self.liabilityAccounts.values(): - print(a.getName(), " -> %.2f" % a.getBalance()) - for c in self.contracts.allLiabilities: - print("\t", c.getName(me), " > ", c.getValue()) - print("TOTAL LIABILITIES: %.2f" % self.getLiabilityValue()) - print("\nTOTAL EQUITY: %.2f" % self.getEquityValue()) - - print("\nSummary of encumbered collateral:") - # for (Contract contract : getLiabilitiesOfType(Repo.class)) { - # ((Repo) contract).printCollateral(); - # } - print("\n\nTotal cash: ", self.getGoodsAccount("cash").getBalance()) - # print("Encumbered cash: "+me.getEncumberedCash()); - # print("Unencumbered cash: " + (me.getCash_() - me.getEncumberedCash())); - - def getInitialEquity(self): - return self.initialEquity - - def setInitialValues(self): - self.initialEquity = self.getEquityValue() - - def getAccountFromContract(self, contract): - return self.assetAccounts.get(contract) - - def getCashAccount(self): - return self["money"] - - # if an Asset loses value, I must debit equity and credit asset - # @param valueLost the value lost - def devalueAsset(self, asset, valueLost): - self.assetAccounts.get(asset).credit(valueLost) - - # Todo: perform a check here that the Asset account balances match the value of the assets. (?) - - def appreciateAsset(self, asset, valueLost): - self.assetAccounts.get(asset).debit(valueLost) - - def devalueLiability(self, liability, valueLost): - self.liabilityAccounts.get(liability).debit(valueLost) - - def appreciateLiability(self, liability, valueLost): - self.liabilityAccounts.get(liability).credit(valueLost) - - - From 9e815bea691d85390e693afea9daaed2982a785b Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 08:09:26 -0300 Subject: [PATCH 06/17] Fixes imports --- economicsl/__init__.py | 2 +- economicsl/accounttype.py | 9 +++++++++ economicsl/action.py | 17 +++-------------- economicsl/agent.py | 13 ++++--------- economicsl/contracts.py | 7 +------ economicsl/ledger.py | 5 +++++ 6 files changed, 23 insertions(+), 30 deletions(-) create mode 100644 economicsl/accounttype.py diff --git a/economicsl/__init__.py b/economicsl/__init__.py index f821dd0..f72bfc8 100644 --- a/economicsl/__init__.py +++ b/economicsl/__init__.py @@ -3,7 +3,7 @@ from .obligations import ObligationMessage, ObligationsAndGoodsMailbox from .obligations import Obligation -from .accounting import AccountType # NOQA +from .accounttype import AccountType # NOQA from abce import NotEnoughGoods # NOQA import abce from .agent import Agent diff --git a/economicsl/accounttype.py b/economicsl/accounttype.py new file mode 100644 index 0000000..18a6bbf --- /dev/null +++ b/economicsl/accounttype.py @@ -0,0 +1,9 @@ +import enum + + +class AccountType(enum.Enum): + ASSET=1 + LIABILITY=2 + INCOME=4 + EXPENSES=5 + GOOD=6 diff --git a/economicsl/action.py b/economicsl/action.py index 429b8c6..a4cdbc1 100644 --- a/economicsl/action.py +++ b/economicsl/action.py @@ -1,18 +1,7 @@ -from typing import List - -from .accounting import Ledger -from .obligations import ObligationMessage, ObligationsAndGoodsMailbox - -from .obligations import Obligation -from .accounting import AccountType # NOQA -from abce import NotEnoughGoods # NOQA -import abce -from .agent import Agent - class Action: - def __init__(self, me: Agent) -> None: + def __init__(self, me) -> None: self.me = me - self.amount = np.longdouble(0.0) + self.amount = 0.0 def perform(self) -> None: print("Model.actionsRecorder.recordAction(this); not called because deleted") @@ -33,7 +22,7 @@ def print(self, actions=None) -> None: print("Action", counter, "->", action.getName()) counter += 1 - def getAgent(self) -> Agent: + def getAgent(self): return self.me def getSimulation(self): diff --git a/economicsl/agent.py b/economicsl/agent.py index 1e2f298..3f31cfd 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -1,10 +1,10 @@ from typing import List -from .accounting import Ledger +from .ledger import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox from .obligations import Obligation -from .accounting import AccountType # NOQA +from .accounttype import AccountType # NOQA from abce import NotEnoughGoods # NOQA import abce @@ -15,10 +15,7 @@ def __init__(self, *args, **kwargs): self.alive = True self.mainLedger = Ledger(self, self._haves) self.obligationsAndGoodsMailbox = ObligationsAndGoodsMailbox(self) - - def init(self, agent_parameters, sim_parameters): - super().init(agent_parameters, sim_parameters) - self.simulation = sim_parameters + self.simulation = kwargs['simulation_parameters'] def _begin_subround(self): super()._begin_subround() @@ -33,7 +30,7 @@ def add(self, contract) -> None: self.mainLedger.addLiability(contract) else: print(contract, contract.getAssetParty(), contract.getLiabilityParty(), (self.name)) - raise Exception("who the fuck is this" ) + raise Exception("who the fuck is this") def getName(self): return str(self.name) @@ -67,7 +64,6 @@ def sendObligation(self, recipient, obligation: Obligation) -> None: msg = ObligationMessage(self, obligation) self.message(recipient.group, recipient.id, '!oblmsg', msg) - def printMailbox(self) -> None: self.obligationsAndGoodsMailbox.printMailbox() @@ -77,6 +73,5 @@ def message(self, receiver, topic, content, overload=None): receiver = receiver.name super().message(receiver[0], receiver[1], topic, content) - def get_obligation_outbox(self) -> List[Obligation]: return self.obligationsAndGoodsMailbox.getObligation_outbox() diff --git a/economicsl/contracts.py b/economicsl/contracts.py index 7fb1da8..3241dec 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -1,14 +1,9 @@ -from abce import NotEnoughGoods -from .account import Account -from ledger import Ledger - def doubleEntry(debitAccount, creditAccount, amount): debitAccount.debit(amount) creditAccount.credit(amount) + class Contracts: def __init__(self): self.allAssets = [] self.allLiabilities = [] - - diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 6e1cded..3a6ce03 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -9,6 +9,11 @@ # # A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books # (as in branch banking for example). +from .contracts import Contracts +from .account import Account +from .accounttype import AccountType + + class Ledger: def __init__(self, me, inventory) -> None: # A StressLedger is a list of accounts (for quicker searching) From 5375c8ebf0315f44c2e9e5d4f73c62bc1efa90be Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 13:22:49 -0300 Subject: [PATCH 07/17] Removes inconsistent second definition of AccountType --- economicsl/account.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/economicsl/account.py b/economicsl/account.py index 19551c1..b968ded 100644 --- a/economicsl/account.py +++ b/economicsl/account.py @@ -1,3 +1,6 @@ +from .accounttype import AccountType + + class Account: def __init__(self, name, accountType, startingBalance=0.0) -> None: self.name = name @@ -24,16 +27,5 @@ def getAccountType(self): def getBalance(self): return self.balance - def getName(self) -> str: + def getName(self): return self.name - - -def enum(**enums): - return type('Enum', (), enums) - - -AccountType = enum(ASSET=1, - LIABILITY=2, - INCOME=4, - EXPENSES=5, - GOOD=6) From 3155b48abed255780093d4fc8a25c8c4e5a7cbdb Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 13:36:02 -0300 Subject: [PATCH 08/17] Changes allAssets and allLiabilities to dicts of the contracts type for faster lookup --- economicsl/contracts.py | 5 +++-- economicsl/ledger.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/economicsl/contracts.py b/economicsl/contracts.py index 3241dec..32f1070 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -2,8 +2,9 @@ def doubleEntry(debitAccount, creditAccount, amount): debitAccount.debit(amount) creditAccount.credit(amount) +from collections import defaultdict class Contracts: def __init__(self): - self.allAssets = [] - self.allLiabilities = [] + self.allAssets = defaultdict(list) + self.allLiabilities = defaultdict(list) diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 3a6ce03..ebb720c 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -42,23 +42,23 @@ def getEquityValue(self): def getAssetValueOf(self, contractType): # return assetAccounts.get(contractType).getBalance(); - return sum([c.getValue() for c in self.contracts.allAssets if isinstance(c, contractType)]) + return sum((c.getValue() for c in self.contracts.allAssets[contractType])) def getLiabilityValueOf(self, contractType): # return liabilityAccounts.get(contractType).getBalance(); - return sum([c.getValue() for c in self.contracts.allLiabilities if isinstance(c, contractType)]) + return sum((c.getValue() for c in self.contracts.allLiabilities[contractType])) def getAllAssets(self): - return self.contracts.allAssets + return [item for sublist in self.contracts.allAssets.values() for item in sublist] def getAllLiabilities(self): - return self.contracts.allLiabilities + return [item for sublist in self.contracts.allLiabilities.values() for item in sublist] def getAssetsOfType(self, contractType): - return [c for c in self.contracts.allAssets if isinstance(c, contractType)] + return [c for c in self.contracts.allAssets[contractType]] def getLiabilitiesOfType(self, contractType): - return [c for c in self.contracts.allLiabilities if isinstance(c, contractType)] + return [c for c in self.contracts.allLiabilities[contractType]] def addAccount(self, account, contractType): switch = account.getAccountType() @@ -82,7 +82,7 @@ def addAsset(self, contract): assetAccount.debit(contract.getValue()) - self.contracts.allAssets.append(contract) + self.contracts.allAssets[type(contract)].append(contract) # Adding a liability means debiting equity and crediting the account # relevant to that type of contract. @@ -98,7 +98,7 @@ def addLiability(self, contract): liabilityAccount.credit(contract.getValue()) # Add to the general inventory? - self.contracts.allLiabilities.append(contract) + self.contracts.allLiabilities[type(contract)].append(contract) def create(self, name, amount, value): self.inventory.create(name, amount) From 9743cbe42bd2cf224a356342dbef7e3e23409de4 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 13:36:59 -0300 Subject: [PATCH 09/17] Removes doubleEntry (double_entry) from contracts.py --- economicsl/contracts.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/economicsl/contracts.py b/economicsl/contracts.py index 32f1070..5d2fe58 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -1,6 +1,3 @@ -def doubleEntry(debitAccount, creditAccount, amount): - debitAccount.debit(amount) - creditAccount.credit(amount) from collections import defaultdict From 6ed1baaa5b9b0a7703c8df50010c41906401cc59 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 16:26:08 -0300 Subject: [PATCH 10/17] removed numpy --- economicsl/agent.py | 3 +-- economicsl/ledger.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/economicsl/agent.py b/economicsl/agent.py index 3f31cfd..23cb827 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -1,5 +1,4 @@ from typing import List - from .ledger import Ledger from .obligations import ObligationMessage, ObligationsAndGoodsMailbox @@ -30,7 +29,7 @@ def add(self, contract) -> None: self.mainLedger.addLiability(contract) else: print(contract, contract.getAssetParty(), contract.getLiabilityParty(), (self.name)) - raise Exception("who the fuck is this") + raise Exception("who the fuck is this" ) def getName(self): return str(self.name) diff --git a/economicsl/ledger.py b/economicsl/ledger.py index ebb720c..7a8991b 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -1,4 +1,3 @@ - # This is the main class implementing double entry org.economicsl.accounting. All public operations provided by this class # are performed as a double entry operation, i.e. a pair of (dr, cr) operations. # From 7cf3d400e8714d468a1ba9caec83bfb69d693da4 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 08:09:26 -0300 Subject: [PATCH 11/17] Fixes imports --- economicsl/agent.py | 2 +- economicsl/contracts.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/economicsl/agent.py b/economicsl/agent.py index 23cb827..527ba25 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -29,7 +29,7 @@ def add(self, contract) -> None: self.mainLedger.addLiability(contract) else: print(contract, contract.getAssetParty(), contract.getLiabilityParty(), (self.name)) - raise Exception("who the fuck is this" ) + raise Exception("who the fuck is this") def getName(self): return str(self.name) diff --git a/economicsl/contracts.py b/economicsl/contracts.py index 5d2fe58..a38c44e 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -1,6 +1,6 @@ - from collections import defaultdict + class Contracts: def __init__(self): self.allAssets = defaultdict(list) From 4ccad95840fb4c8a2f8a0eef9098b6bb9ac0e39f Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Wed, 6 Sep 2017 19:57:51 -0300 Subject: [PATCH 12/17] Creates documentation --- docs/conf.py | 186 +++++++++++++++++++++++++++++++++++++ docs/index.rst | 20 ++++ docs/source/conf.rst | 7 ++ docs/source/economicsl.rst | 94 +++++++++++++++++++ docs/source/modules.rst | 7 ++ 5 files changed, 314 insertions(+) create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/source/conf.rst create mode 100644 docs/source/economicsl.rst create mode 100644 docs/source/modules.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..7ec668c --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# abcESL documentation build configuration file, created by +# sphinx-quickstart on Wed Sep 6 19:39:15 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'abcESL' +copyright = '2017, Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer' +author = 'Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'abcESLdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'abcESL.tex', 'abcESL Documentation', + 'Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'abcesl', 'abcESL Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'abcESL', 'abcESL Documentation', + author, 'abcESL', 'One line description of project.', + 'Miscellaneous'), +] + + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..481769d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +.. abcESL documentation master file, created by + sphinx-quickstart on Wed Sep 6 19:39:15 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to abcESL's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/conf.rst b/docs/source/conf.rst new file mode 100644 index 0000000..b852516 --- /dev/null +++ b/docs/source/conf.rst @@ -0,0 +1,7 @@ +conf module +=========== + +.. automodule:: conf + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/economicsl.rst b/docs/source/economicsl.rst new file mode 100644 index 0000000..c69989f --- /dev/null +++ b/docs/source/economicsl.rst @@ -0,0 +1,94 @@ +economicsl package +================== + +Submodules +---------- + +economicsl\.account module +-------------------------- + +.. automodule:: economicsl.account + :members: + :undoc-members: + :show-inheritance: + +economicsl\.accounttype module +------------------------------ + +.. automodule:: economicsl.accounttype + :members: + :undoc-members: + :show-inheritance: + +economicsl\.action module +------------------------- + +.. automodule:: economicsl.action + :members: + :undoc-members: + :show-inheritance: + +economicsl\.agent module +------------------------ + +.. automodule:: economicsl.agent + :members: + :undoc-members: + :show-inheritance: + +economicsl\.contract module +--------------------------- + +.. automodule:: economicsl.contract + :members: + :undoc-members: + :show-inheritance: + +economicsl\.contracts module +---------------------------- + +.. automodule:: economicsl.contracts + :members: + :undoc-members: + :show-inheritance: + +economicsl\.ledger module +------------------------- + +.. automodule:: economicsl.ledger + :members: + :undoc-members: + :show-inheritance: + +economicsl\.obligationmessage module +------------------------------------ + +.. automodule:: economicsl.obligationmessage + :members: + :undoc-members: + :show-inheritance: + +economicsl\.obligations module +------------------------------ + +.. automodule:: economicsl.obligations + :members: + :undoc-members: + :show-inheritance: + +economicsl\.obligationsmailbox module +------------------------------------- + +.. automodule:: economicsl.obligationsmailbox + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: economicsl + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/modules.rst b/docs/source/modules.rst new file mode 100644 index 0000000..14c33e7 --- /dev/null +++ b/docs/source/modules.rst @@ -0,0 +1,7 @@ +economicsl +========== + +.. toctree:: + :maxdepth: 4 + + economicsl From 3a07322127c9a58b865439627e681946f81937b2 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 14:29:59 -0300 Subject: [PATCH 13/17] Set empty accounts startvalue to int(0) not float(0), to enable integer parameterization --- economicsl/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/economicsl/account.py b/economicsl/account.py index b968ded..c83b277 100644 --- a/economicsl/account.py +++ b/economicsl/account.py @@ -2,7 +2,7 @@ class Account: - def __init__(self, name, accountType, startingBalance=0.0) -> None: + def __init__(self, name, accountType, startingBalance=0) -> None: self.name = name self.accountType = accountType self.balance = startingBalance From e514dd04a6a227bb9f4324e9998e16a2a5bc35f6 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 16:12:18 -0300 Subject: [PATCH 14/17] Change account to use type(liability)] --- economicsl/__init__.py | 6 +- economicsl/agent.py | 101 ++++++++++++++++++++++++------- economicsl/bankersrounding.py | 12 ---- economicsl/ledger.py | 96 +++++++++++++++++++++-------- economicsl/obligations.py | 3 - economicsl/obligationsmailbox.py | 2 +- 6 files changed, 155 insertions(+), 65 deletions(-) delete mode 100644 economicsl/bankersrounding.py diff --git a/economicsl/__init__.py b/economicsl/__init__.py index f72bfc8..d668a76 100644 --- a/economicsl/__init__.py +++ b/economicsl/__init__.py @@ -1,7 +1,8 @@ +""" abcesl is an Contracting and accounting plugin for ABCE. ABCE is the agent-based computational +economics library. www.github.com/AB-CE/abce. +""" from typing import List from .ledger import Ledger -from .obligations import ObligationMessage, ObligationsAndGoodsMailbox - from .obligations import Obligation from .accounttype import AccountType # NOQA from abce import NotEnoughGoods # NOQA @@ -9,4 +10,3 @@ from .agent import Agent from .action import Action from .contract import Contract -from .bankersrounding import BankersRounding diff --git a/economicsl/agent.py b/economicsl/agent.py index 527ba25..f65467c 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -1,26 +1,35 @@ +""" test """ from typing import List from .ledger import Ledger -from .obligations import ObligationMessage, ObligationsAndGoodsMailbox - +from .obligationmessage import ObligationMessage +from .obligationsmailbox import ObligationsMailbox from .obligations import Obligation from .accounttype import AccountType # NOQA from abce import NotEnoughGoods # NOQA import abce +from math import isclose class Agent(abce.Agent): + """ The abcesl.Agent inherits from abce.Agent, it additionally provides + contracting and accounting capabilities """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.alive = True self.mainLedger = Ledger(self, self._haves) - self.obligationsAndGoodsMailbox = ObligationsAndGoodsMailbox(self) + self.inventory = self.mainLedger.inventory + self.obligationsMailbox = ObligationsMailbox(self) self.simulation = kwargs['simulation_parameters'] def _begin_subround(self): + """ This means step is called every new subround in ABCE, it's + from the plugin API """ super()._begin_subround() - self.step() + self._step() - def add(self, contract) -> None: + def add(self, contract): + """ Adds a contract to the agent's ledger, figures out + whether the contract is an asset or an liability """ if (contract.getAssetParty() == self.name): # This contract is an asset for me. self.mainLedger.addAsset(contract) @@ -32,45 +41,93 @@ def add(self, contract) -> None: raise Exception("who the fuck is this") def getName(self): + """ depreciated """ return str(self.name) def getTime(self): + """ depreciated """ return self.simulation.time def getSimulation(self): return self.simulation - def isAlive(self) -> bool: + def isAlive(self): return self.alive - def addCash(self, amount) -> None: - self.mainLedger.inventory.create('money', amount) + def addCash(self, amount): + """ depreciated """ + self.create('money', amount, 1) + + def create(self, good, amount, value): + """ Creates a good and books it into the according account with + the value value + + Args: + good + + amount + + value + This function overwrites the abce.Agent's create function""" + self.mainLedger.create(good, amount, value) + + def destroy(self, good, amount, value=None): + """ deletes a good and books it into the according account. + If no value is specified, the book value is calculated: + account_value / number of goods. + + Args: + good + + amount + + value (optional) + + This function overwrites the abce.Agent's delete function""" + self.mainLedger.destroy(good, amount, value) def getCash_(self): + """ depreciated """ return self.mainLedger.inventory['money'] - def getMainLedger(self) -> Ledger: + def getMainLedger(self): + """ depreciated """ return self.mainLedger - def step(self) -> None: - self.obligationsAndGoodsMailbox.step() + def _step(self): + """ Processes received obligations """ + self.obligationsMailbox._step() - def sendObligation(self, recipient, obligation: Obligation) -> None: + def sendObligation(self, recipient, obligation): + """ sends an obligation to an agent """ if isinstance(obligation, ObligationMessage): - self.message(recipient.group, recipient.id, '!oblmsg', obligation) - self.obligationsAndGoodsMailbox.addToObligationOutbox(obligation) + self.send(recipient.group, '!oblmsg', obligation) + self.obligationsMailbox.addToObligationOutbox(obligation) else: msg = ObligationMessage(self, obligation) - self.message(recipient.group, recipient.id, '!oblmsg', msg) + self.send(recipient, '!oblmsg', msg) - def printMailbox(self) -> None: - self.obligationsAndGoodsMailbox.printMailbox() - - def message(self, receiver, topic, content, overload=None): + def printMailbox(self): + """ Prints all obligations in the mailbox """ + self.obligationsMailbox.printMailbox() + def message(self, receiver, topic, content): + """ depreciated use ABCE's send method """ if not isinstance(receiver, tuple): receiver = receiver.name - super().message(receiver[0], receiver[1], topic, content) + super().send(receiver, topic, content) + + def get_obligation_outbox(self): + """ Prints all obligations in the outbox """ + return self.obligationsMailbox.getObligation_outbox() + + def sell_asset(self, asset, quantity, value, price, receiver=None): + """ Sells a quantity of an asset for a price. And books it + accordingly. If no receiver is specified, the good is sold to + a party outside of the model. + """ + self.ledger.sell_asset(quantity, asset) + + + - def get_obligation_outbox(self) -> List[Obligation]: - return self.obligationsAndGoodsMailbox.getObligation_outbox() diff --git a/economicsl/bankersrounding.py b/economicsl/bankersrounding.py deleted file mode 100644 index 920b167..0000000 --- a/economicsl/bankersrounding.py +++ /dev/null @@ -1,12 +0,0 @@ -class BankersRounding: - def bankersRounding(self, value) -> int: - s = int(value) - t = abs(value - s) - - if ((t < 0.5) or (t == 0.5 and s % 2 == 0)): - return s - else: - if (value < 0): - return s - 1 - else: - return s + 1 diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 7a8991b..6d18d1e 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -11,11 +11,11 @@ from .contracts import Contracts from .account import Account from .accounttype import AccountType - +from math import isclose class Ledger: - def __init__(self, me, inventory) -> None: - # A StressLedger is a list of accounts (for quicker searching) + def __init__(self, me, inventory): + """ A StressLedger is a list of accounts (for quicker searching) # Each Account includes an inventory to hold one type of contract. # These hashmaps are used to access the correct account for a given type of contract. @@ -57,9 +57,14 @@ def getAssetsOfType(self, contractType): return [c for c in self.contracts.allAssets[contractType]] def getLiabilitiesOfType(self, contractType): - return [c for c in self.contracts.allLiabilities[contractType]] - - def addAccount(self, account, contractType): + """ returns all contracts of a certain type that are booked as liabilities, + whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value. + """ + return self.contracts.allLiabilities[contractType] + + def _addAccount(self, account, contractType): + """ adds a new account """ switch = account.getAccountType() if switch == AccountType.ASSET: self.assetAccounts[contractType] = account @@ -72,12 +77,18 @@ def addAccount(self, account, contractType): # and crediting equity. # @param contract an Asset contract to add def addAsset(self, contract): - assetAccount = self.assetAccounts.get(contract) + """ Adding an asset means debiting the account relevant to that type of contract + and crediting equity. + Args: + + contract: + an Asset contract to add """ + assetAccount = self.assetAccounts.get(type(contract)) if assetAccount is None: # If there doesn't exist an Account to hold this type of contract, we create it assetAccount = Account(contract.getName(self.me), AccountType.ASSET) - self.addAccount(assetAccount, contract) + self._addAccount(assetAccount, type(contract)) assetAccount.debit(contract.getValue()) @@ -87,12 +98,15 @@ def addAsset(self, contract): # relevant to that type of contract. # @param contract a Liability contract to add def addLiability(self, contract): - liabilityAccount = self.liabilityAccounts.get(contract) + """ Adding a liability means debiting equity and crediting the account + relevant to that type of contract. + @param contract a Liability contract to add """ + liabilityAccount = self.liabilityAccounts.get(type(contract)) if liabilityAccount is None: # If there doesn't exist an Account to hold this type of contract, we create it liabilityAccount = Account(contract.getName(self.me), AccountType.LIABILITY) - self.addAccount(liabilityAccount, contract) + self._addAccount(liabilityAccount, type(contract)) liabilityAccount.credit(contract.getValue()) @@ -156,10 +170,17 @@ def payLiability(self, amount, loan): # (dr liability, cr cash ) doubleEntry(self.liabilityAccount, self['money'], amount) - # If I've sold an asset, debit cash and credit asset - # @param amount the *value* of the asset - def sellAsset(self, amount, assetType): - assetAccount = self.assetAccounts.get(assetType) + def sell_asset(self, amount, asset): + """ Books the sales of an asset. + Args: + amount: + the *value* of the asset + """ + assert amount <= asset.quantity + asset.quantity -= amount + if isclose(asset.quantity, 0): + self.contracts.allAssets(type(asset)).remove(asset) + assetAccount = self.assetAccounts[type(asset)] # (dr cash, cr asset) doubleEntry(self["money"], assetAccount, amount) @@ -211,21 +232,48 @@ def getAccountFromContract(self, contract): def getCashAccount(self): return self["money"] - # if an Asset loses value, I must debit equity and credit asset - # @param valueLost the value lost - def devalueAsset(self, asset, valueLost): - self.assetAccounts.get(asset).credit(valueLost) + def devalueAsset(self, asset, delta_value): + """ if an Asset loses value, I must debit equity and credit asset + Args: + asset + + delta_value: + the value lost + """ + self.assetAccounts[type(asset)].credit(delta_value) # Todo: perform a check here that the Asset account balances match the value of the assets. (?) - def appreciateAsset(self, asset, valueLost): - self.assetAccounts.get(asset).debit(valueLost) + def appreciateAsset(self, asset, delta_value): + """ appreciates a certain asset + Args: + asset + + delta_value: + the value gained + """ + self.assetAccounts[type(asset)].debit(delta_value) + + def devalueLiability(self, liability, delta_value): + """ devalues a certain liability + Args: + liability + + delta_value: + the value lost + """ + self.liabilityAccounts[type(liability)].debit(delta_value) + + def appreciateLiability(self, liability, delta_value): + """ appreciates a certain liability + Args: + liability - def devalueLiability(self, liability, valueLost): - self.liabilityAccounts.get(liability).debit(valueLost) + delta_value: + the value gained + """ - def appreciateLiability(self, liability, valueLost): - self.liabilityAccounts.get(liability).credit(valueLost) + self.liabilityAccounts[type(liability)].credit(delta_value) diff --git a/economicsl/obligations.py b/economicsl/obligations.py index ca4b2c8..8b2840a 100644 --- a/economicsl/obligations.py +++ b/economicsl/obligations.py @@ -1,6 +1,3 @@ -from .obligationmessage import ObligationMessage -from .obligationsmailbox import ObligationsMailbox as ObligationsAndGoodsMailbox - class Obligation: def __init__(self, contract, amount, timeLeftToPay: int) -> None: self.amount = amount diff --git a/economicsl/obligationsmailbox.py b/economicsl/obligationsmailbox.py index 2071e8f..68d6d16 100644 --- a/economicsl/obligationsmailbox.py +++ b/economicsl/obligationsmailbox.py @@ -32,7 +32,7 @@ def fulfilMaturedRequests(self) -> None: if o.isDue() and not o.isFulfilled(): o.fulfil() - def step(self) -> None: + def _step(self) -> None: self.obligation_inbox.extend(self.owner.get_messages('!oblmsg')) # Remove all fulfilled requests self.obligation_inbox = [o for o in self.obligation_inbox if not o.isFulfilled()] From bfc579375a666517ef6abed837715148c7950fa4 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 16:13:27 -0300 Subject: [PATCH 15/17] assets and libability inventory sorted by contract type --- economicsl/ledger.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 6d18d1e..9d63ddd 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -22,16 +22,21 @@ def __init__(self, me, inventory): # Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract # type (such as Loan) can sometimes be an asset and sometimes a liability. - # A book is initially created with a cash account (it's the simplest possible book) - self.assetAccounts = {} # a hashmap from a contract to a assetAccount + A book is initially created with a cash account (it's the simplest possible book) + """ + self.assetAccounts = {} # a dict from a contract to a assetAccount self.inventory = inventory self.contracts = Contracts() self.goodsAccounts = {} - self.liabilityAccounts = {} # a hashmap from a contract to a liabilityAccount + self.liabilityAccounts = {} # a dict from a contract to a liabilityAccount self.me = me def getAssetValue(self): - return sum([aa.getBalance() for aa in self.assetAccounts.values()]) + """ Value of all assets and goods. + Whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value """ + return (sum([aa.getBalance() for aa in self.assetAccounts.values()]) + + sum([aa.getBalance() for aa in self.goodsAccounts.values()])) def getLiabilityValue(self): return sum([la.getBalance() for la in self.liabilityAccounts.values()]) @@ -54,7 +59,11 @@ def getAllLiabilities(self): return [item for sublist in self.contracts.allLiabilities.values() for item in sublist] def getAssetsOfType(self, contractType): - return [c for c in self.contracts.allAssets[contractType]] + """ returns all contracts of a certain type that are booked as assets, + whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value. + """ + return self.contracts.allAssets[contractType] def getLiabilitiesOfType(self, contractType): """ returns all contracts of a certain type that are booked as liabilities, @@ -67,8 +76,10 @@ def _addAccount(self, account, contractType): """ adds a new account """ switch = account.getAccountType() if switch == AccountType.ASSET: + assert contractType not in self.assetAccounts self.assetAccounts[contractType] = account elif switch == AccountType.LIABILITY: + assert contractType not in self.liabilityAccounts self.liabilityAccounts[contractType] = account # Not sure what to do with INCOME, EXPENSES From 012cc2eb3cdf72aa3512bbf8a80fd53eeca3c9d5 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Mon, 11 Sep 2017 16:13:56 -0300 Subject: [PATCH 16/17] Documentation --- docs/Makefile | 20 +++++++ docs/account.rst | 8 +++ docs/action.rst | 0 docs/agent.rst | 6 ++ docs/bankersrounding.rst | 6 ++ docs/conf.py | 52 ++++++----------- docs/contract.rst | 6 ++ docs/contracting.rst | 6 ++ docs/exception.rst | 6 ++ docs/index.rst | 34 ++++++++++- docs/ledger.rst | 10 ++++ docs/obligationmessage.rst | 6 ++ docs/obligations.rst | 6 ++ docs/source/conf.rst | 7 --- docs/source/modules.rst | 5 ++ economicsl/account.py | 6 +- economicsl/accounttype.py | 1 + economicsl/contract.py | 6 ++ economicsl/contracts.py | 4 ++ economicsl/ledger.py | 117 +++++++++++++++++++++++++++---------- 20 files changed, 237 insertions(+), 75 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/account.rst create mode 100644 docs/action.rst create mode 100644 docs/agent.rst create mode 100644 docs/bankersrounding.rst create mode 100644 docs/contract.rst create mode 100644 docs/contracting.rst create mode 100644 docs/exception.rst create mode 100644 docs/ledger.rst create mode 100644 docs/obligationmessage.rst create mode 100644 docs/obligations.rst delete mode 100644 docs/source/conf.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..523a5cc --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python3 -msphinx +SPHINXPROJ = abcESL +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/account.rst b/docs/account.rst new file mode 100644 index 0000000..7a8571d --- /dev/null +++ b/docs/account.rst @@ -0,0 +1,8 @@ +Account +======= + +.. automodule:: economicsl.accounttype + +.. automodule:: economicsl.account + :members: + :show-inheritance: diff --git a/docs/action.rst b/docs/action.rst new file mode 100644 index 0000000..e69de29 diff --git a/docs/agent.rst b/docs/agent.rst new file mode 100644 index 0000000..34fa918 --- /dev/null +++ b/docs/agent.rst @@ -0,0 +1,6 @@ +abcesl.Agent +============ + +.. automodule:: economicsl.agent + :members: + :show-inheritance: diff --git a/docs/bankersrounding.rst b/docs/bankersrounding.rst new file mode 100644 index 0000000..f56b451 --- /dev/null +++ b/docs/bankersrounding.rst @@ -0,0 +1,6 @@ +Bankersrounding +=============== + +.. automodule:: economicsl.bankersrounding + :members: + :show-inheritance: diff --git a/docs/conf.py b/docs/conf.py index 7ec668c..11d91a9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # abcESL documentation build configuration file, created by -# sphinx-quickstart on Wed Sep 6 19:39:15 2017. +# sphinx-quickstart on Wed Sep 6 20:33:17 2017. # # This file is execfile()d with the current directory set to its # containing dir. @@ -31,12 +31,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -53,7 +48,7 @@ # General information about the project. project = 'abcESL' copyright = '2017, Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer' -author = 'Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer' +author = 'R. Tanin, D. Taghawi-Nejad, A. Kleinnijenhuis, T. Wetzer, D. Farmer' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -101,6 +96,21 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + # -- Options for HTMLHelp output ------------------------------------------ @@ -133,7 +143,7 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'abcESL.tex', 'abcESL Documentation', - 'Rudy Tanin, Davoud Taghawi-Nejad, Alissa Kleinnijenhuis, Thom Wetzer', 'manual'), + 'R. Tanin, D. Taghawi-Nejad, A. Kleinnijenhuis, T. Wetzer, D. Farmer', 'manual'), ] @@ -160,27 +170,3 @@ -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} diff --git a/docs/contract.rst b/docs/contract.rst new file mode 100644 index 0000000..88e6b43 --- /dev/null +++ b/docs/contract.rst @@ -0,0 +1,6 @@ +Contract +======== + +.. automodule:: economicsl.contract + :members: + :show-inheritance: diff --git a/docs/contracting.rst b/docs/contracting.rst new file mode 100644 index 0000000..28d6705 --- /dev/null +++ b/docs/contracting.rst @@ -0,0 +1,6 @@ +Contracting +=========== + +.. automodule:: abce.contracts.flexiblecontracting + :members: + :show-inheritance: diff --git a/docs/exception.rst b/docs/exception.rst new file mode 100644 index 0000000..4f630ed --- /dev/null +++ b/docs/exception.rst @@ -0,0 +1,6 @@ +Exception +========= + +.. autoexception:: abce.NotEnoughGoods + :members: + :show-inheritance: diff --git a/docs/index.rst b/docs/index.rst index 481769d..218b7dd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,16 +1,48 @@ .. abcESL documentation master file, created by - sphinx-quickstart on Wed Sep 6 19:39:15 2017. + sphinx-quickstart on Wed Sep 6 20:33:17 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to abcESL's documentation! ================================== +Introduction +------------ + +ABCESL is an accounting and contracting framework for ABCE. It was originally developed by +Rafa Babtista, Alissa Kleinnijenhuis and Thom Wetzer and subsequently distilled and refined by +Rudy Tanin, Davoud Taghawi-Nejad. Originally it was conceived as a framework to run +systemic stress testing exercises for the financial sector. +Currently it is used in the resilience project as well as in an insurance modeling +project. + +In order to install abcesl, first install ABCE (abce.readthedocs.io) and then +install :code:`sudo python3 -m pip install abcesl` or +:code:`sudo pypy3 -m pip install abcesl`. + +You can than create agents that inherit from abcesl.Agent instead of abce.Agent. +Like abce agents these agents have an inventory of goods :code:`self['cookies']`, +but they also have an inventory of contracts and set of accounts that book the +values of goods and contracts. + +ABCESL is part of the wider effort of the Economic Simulation Library at +the Institute for New Economic Thinking at Oxford University. + + .. toctree:: :maxdepth: 2 :caption: Contents: + agent + ledger + account + contract + contracting + obligations + obligationmessage + exception + Indices and tables ================== diff --git a/docs/ledger.rst b/docs/ledger.rst new file mode 100644 index 0000000..9f80a8e --- /dev/null +++ b/docs/ledger.rst @@ -0,0 +1,10 @@ +Ledger +====== + +.. automodule:: economicsl.ledger + +.. autoclass:: economicsl.ledger.Ledger + :members: + :show-inheritance: + + diff --git a/docs/obligationmessage.rst b/docs/obligationmessage.rst new file mode 100644 index 0000000..943c14d --- /dev/null +++ b/docs/obligationmessage.rst @@ -0,0 +1,6 @@ +ObligationsMessage +================== + +.. automodule:: economicsl.obligationmessage + :members: + :show-inheritance: diff --git a/docs/obligations.rst b/docs/obligations.rst new file mode 100644 index 0000000..551ddf5 --- /dev/null +++ b/docs/obligations.rst @@ -0,0 +1,6 @@ +Obligations +=========== + +.. automodule:: economicsl.obligations + :members: + :show-inheritance: diff --git a/docs/source/conf.rst b/docs/source/conf.rst deleted file mode 100644 index b852516..0000000 --- a/docs/source/conf.rst +++ /dev/null @@ -1,7 +0,0 @@ -conf module -=========== - -.. automodule:: conf - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 14c33e7..a0fdc50 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -1,6 +1,11 @@ economicsl ========== + + +Sep 06, 2017 +W + .. toctree:: :maxdepth: 4 diff --git a/economicsl/account.py b/economicsl/account.py index c83b277..8e660f2 100644 --- a/economicsl/account.py +++ b/economicsl/account.py @@ -2,20 +2,22 @@ class Account: + """ An account either an ASSET, LIABILITY, INCOME, EXPENSES or GOOD account. + It's value can be increase or decreased according to a double entry operation """ def __init__(self, name, accountType, startingBalance=0) -> None: self.name = name self.accountType = accountType self.balance = startingBalance - # A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. def debit(self, amount): + """ A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. """ if (self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES): self.balance += amount else: self.balance -= amount - # A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. def credit(self, amount): + """ A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. """ if ((self.accountType == AccountType.ASSET) or (self.accountType == AccountType.EXPENSES)): self.balance -= amount else: diff --git a/economicsl/accounttype.py b/economicsl/accounttype.py index 18a6bbf..0d004ab 100644 --- a/economicsl/accounttype.py +++ b/economicsl/accounttype.py @@ -1,3 +1,4 @@ +""" There are 5 account types in the AccountType class ASSET, LIABILITY, INCOME, EXPENSES, GOOD """ import enum diff --git a/economicsl/contract.py b/economicsl/contract.py index 5c14761..ae2b799 100644 --- a/economicsl/contract.py +++ b/economicsl/contract.py @@ -1,11 +1,17 @@ class Contract: + """ a contract must minimally implement the following functions""" def getAssetParty(self): + """ Returns the asset party as a tuple (group, ID) e.G. ('bank', 5) """ pass def getLiabilityParty(self): + """ Returns the liability party as a tuple (group, ID) e.G. ('bank', 5) """ pass def getValue(self, me): + """ Returns the value of the contract conditional on the :code:`me` parameter. + Me is a name tuple to determine, whether the valuating is from the perspective + of the asset or liability side """ pass def getAvailableActions(self, me): diff --git a/economicsl/contracts.py b/economicsl/contracts.py index a38c44e..4e9a82b 100644 --- a/economicsl/contracts.py +++ b/economicsl/contracts.py @@ -2,6 +2,10 @@ class Contracts: + """ Collection of all contracts an agent holds. Contracts can be on the + asset or the liability side """ def __init__(self): self.allAssets = defaultdict(list) + """ contains all assets in a dictionary, with the assettype as a key """ self.allLiabilities = defaultdict(list) + """ contains all liabilities in a dictionary, with the assettype as a key """ diff --git a/economicsl/ledger.py b/economicsl/ledger.py index 9d63ddd..7bf2835 100644 --- a/economicsl/ledger.py +++ b/economicsl/ledger.py @@ -1,13 +1,24 @@ -# This is the main class implementing double entry org.economicsl.accounting. All public operations provided by this class -# are performed as a double entry operation, i.e. a pair of (dr, cr) operations. -# -# A Ledger contains a set of accounts, and is the interface between an agent and its accounts. Agents cannot -# directly interact with accounts other than via a Ledger. -# -# At the moment, a Ledger contains an account for each type of contract, plus an equity account and a cash account. -# -# A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books -# (as in branch banking for example). +""" This is the main class implementing double entry org.economicsl.accounting. All public operations provided by this class +are performed as a double entry operation, i.e. a pair of (dr, cr) operations. + +A Ledger contains a set of accounts, and is the interface between an agent and its accounts. Agents cannot +directly interact with accounts other than via a Ledger. + +At the moment, a Ledger contains an account for each type of contract, plus an equity account and a cash account. +It also has a book for every type of good it holds. Good accounts are not to be confused with the inventory. + +A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books +(as in branch banking for example). + +There are three different things: +1. The contracts which is an inventory of contracts. +3. The inventory which is the inventory of physical goods +3. The accounts which running value of each contract type or good type + +Every operation either changes contracts and accounts or inventory and accounts. +Except for reevaluation of a good or a contract which does only affect the accounts. + +""" from .contracts import Contracts from .account import Account from .accounttype import AccountType @@ -17,10 +28,10 @@ class Ledger: def __init__(self, me, inventory): """ A StressLedger is a list of accounts (for quicker searching) - # Each Account includes an inventory to hold one type of contract. - # These hashmaps are used to access the correct account for a given type of contract. - # Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract - # type (such as Loan) can sometimes be an asset and sometimes a liability. + Each Account includes an inventory to hold one type of contract. + These hashmaps are used to access the correct account for a given type of contract. + Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract + type (such as Loan) can sometimes be an asset and sometimes a liability. A book is initially created with a cash account (it's the simplest possible book) """ @@ -39,23 +50,35 @@ def getAssetValue(self): sum([aa.getBalance() for aa in self.goodsAccounts.values()])) def getLiabilityValue(self): + """ Value of all Liabilities. + Whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value """ return sum([la.getBalance() for la in self.liabilityAccounts.values()]) def getEquityValue(self): return self.getAssetValue() - self.getLiabilityValue() def getAssetValueOf(self, contractType): - # return assetAccounts.get(contractType).getBalance(); + """ Evaluates all contracts of a certain type, given that there are treated as + assets, and returns their value + Not to be confused with the book value. + Whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value""" return sum((c.getValue() for c in self.contracts.allAssets[contractType])) def getLiabilityValueOf(self, contractType): - # return liabilityAccounts.get(contractType).getBalance(); + """ Evaluates all contracts of a certain type, given that they are treated + as liabilities, and returns their value. Not to be confused with the book value. + Whether they are booked as assets or liabilities depends on the value at acquisition, + not the current value""" return sum((c.getValue() for c in self.contracts.allLiabilities[contractType])) def getAllAssets(self): + """ returns all contracts assets """ return [item for sublist in self.contracts.allAssets.values() for item in sublist] def getAllLiabilities(self): + """ return all contracts that are liabilities """ return [item for sublist in self.contracts.allLiabilities.values() for item in sublist] def getAssetsOfType(self, contractType): @@ -84,9 +107,6 @@ def _addAccount(self, account, contractType): # Not sure what to do with INCOME, EXPENSES - # Adding an asset means debiting the account relevant to that type of contract - # and crediting equity. - # @param contract an Asset contract to add def addAsset(self, contract): """ Adding an asset means debiting the account relevant to that type of contract and crediting equity. @@ -105,9 +125,6 @@ def addAsset(self, contract): self.contracts.allAssets[type(contract)].append(contract) - # Adding a liability means debiting equity and crediting the account - # relevant to that type of contract. - # @param contract a Liability contract to add def addLiability(self, contract): """ Adding a liability means debiting equity and crediting the account relevant to that type of contract. @@ -121,15 +138,37 @@ def addLiability(self, contract): liabilityAccount.credit(contract.getValue()) - # Add to the general inventory? + self.contracts.allLiabilities[type(contract)].append(contract) def create(self, name, amount, value): + """ Creates a good and books it into the according account with + the value value + + Args: + good + + amount + + value + This function overwrites the abce.Agent's create function """ self.inventory.create(name, amount) physicalthingsaccount = self.getGoodsAccount(name) physicalthingsaccount.debit(amount * value) def destroy(self, name, amount, value=None): + """ deletes a good and books it into the according account. + If no value is specified, the book value is calculated: + account_value / number of goods. + + Args: + good + + amount + + value (optional) + + This function overwrites the abce.Agent's delete function""" if value is None: try: value = self.getPhysicalThingValue(name) @@ -141,6 +180,7 @@ def destroy(self, name, amount, value=None): self.getGoodsAccount(name).credit(amount * value) def getGoodsAccount(self, name): + """ access to a particular goods account """ account = self.goodsAccounts.get(name) if account is None: account = Account(name, AccountType.GOOD) @@ -148,14 +188,15 @@ def getGoodsAccount(self, name): return account def getPhysicalThingValue(self, name): + """ Returns the value of a physical thing """ try: return self.getGoodsAccount(name).getBalance() / self.inventory.getGood(name) except: return 0.0 - # Reevaluates the current stock of phisical goods at a specified value and books - # the change to org.economicsl.accounting def revalueGoods(self, name, value): + """ Reevaluates the current stock of physical goods at a specified value and books. + """ old_value = self.getGoodsAccount(name).getBalance() new_value = self.inventory.getGood(name) * value if (new_value > old_value): @@ -164,16 +205,22 @@ def revalueGoods(self, name, value): self.getGoodsAccount(name).credit(old_value - new_value) def addCash(self, amount) -> None: + """ depreciated adds "money" as a physical good """ # (dr cash, cr equity) self.create("money", amount, 1.0) def subtractCash(self, amount) -> None: self.destroy("money", amount, 1.0) - # Operation to pay back a liability loan; debit liability and credit cash - # @param amount amount to pay back - # @param loan the loan which is being paid back def payLiability(self, amount, loan): + """ Operation to pay back a liability loan; debit liability and credit cash + + Args: + amount: + amount to pay back + + loan: + the loan which is being paid back """ self.liabilityAccount = self.liabilityAccounts.get(loan) assert self.inventory.getCash() >= amount # Pre-condition: liquidity has been raised. @@ -196,16 +243,22 @@ def sell_asset(self, amount, asset): # (dr cash, cr asset) doubleEntry(self["money"], assetAccount, amount) - # Operation to cancel a Loan to someone (i.e. cash in a Loan in the Assets side). - # - # I'm using this for simplicity but note that this is equivalent to selling an asset. - # @param amount the amount of loan that is cancelled def pullFunding(self, amount, loan): + """Operation to cancel a Loan to someone (i.e. cash in a Loan in the Assets side). + + I'm using this for simplicity but note that this is equivalent to selling an asset. + + Args: + amount: + the amount of loan that is cancelled + """ loanAccount = self.getAccountFromContract(loan) # (dr cash, cr asset ) doubleEntry(self.getCashAccount(), loanAccount, amount) + # TODO make sure that adds money def printBalanceSheet(self, me): + """ prints a balance sheet """ print("Asset accounts:\n---------------") for a in self.assetAccounts.values(): print(a.getName(), "-> %.2f" % a.getBalance()) From 0627fd08e2f3604dcb405a3db1fd5189bf96eb48 Mon Sep 17 00:00:00 2001 From: DavoudTaghawiNejad Date: Sun, 15 Oct 2017 11:58:30 +0100 Subject: [PATCH 17/17] Updates self._haves to self._inventory --- economicsl/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/economicsl/agent.py b/economicsl/agent.py index f65467c..4aec3bf 100644 --- a/economicsl/agent.py +++ b/economicsl/agent.py @@ -16,7 +16,7 @@ class Agent(abce.Agent): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.alive = True - self.mainLedger = Ledger(self, self._haves) + self.mainLedger = Ledger(self, self._inventory) self.inventory = self.mainLedger.inventory self.obligationsMailbox = ObligationsMailbox(self) self.simulation = kwargs['simulation_parameters']