<commit_before>"""Controller.py
Contains simulation controller class, and inherent simulation logic.
"""

import copy
import math
import networkx as nx
import pickle
from statistics import Statistics
from bank import Bank
from settings import *


class Controller(object):
    """
    Controls simulation, allocation, and the underlying network.
    """
    def __init__(self, import_network=False, export_network=False, build_network=False):
        self.banks = []  # contains all bank objects
        self.id_to_bank = dict()  # reference map of id's to all banks.
        if build_network:
            self.build_network()
        if export_network:
            self.export_network_to_disk(NETWORK_EXPORT_PATH)
        if import_network:
            self.import_network_from_disk(NETWORK_EXPORT_PATH)  # imports network created earlier by the program
        self.defaulted_banks = 0.0
        #self.trigger(self.banks[50].bank_id) # initial trigger (example)
        #self.export_network_to_disk(FAILED_NETWORK_EXPORT_PATH)  # save network after triggering defaults.
        
    def build_network(self):
        """
        Creates the loan network between banks.
        :rtype : None

        """
        self.create_banks()
        self.allocate_degrees()
        self.allocate_loans()

    def create_banks(self):
        """
        Creates bank objects, stores them in self.banks, and registers them in a self.bank_to_id.
        A part of all banks follows a Pareto distribution, all others a log-normal distribution, in asset size.
        All created banks are registered in the dictionary self.id_to_bank.
        :rtype : None
        """
        for n in xrange(0, NUMBER_OF_BANKS_LOGNORMAL):
            bank_size = Statistics.generate_lognormal_number(LOGNORMAL_MEAN, LOGNOMRAL_STDEV)
            bank = Bank(bank_size)
            self.banks.append(bank)
            self.id_to_bank[id(bank)] = bank

        for n in xrange(0, NUMBER_OF_BANKS_PARETO):
            bank_size = Statistics.generate_pareto_number(PARETO_SCALE, PARETO_SHAPE)
            bank = Bank(bank_size)
            self.banks.append(bank)
            self.id_to_bank[id(bank)] = bank

    def allocate_degrees(self):
        """
        First sort banks by asset size, then match them with sorted degree list.
        Node degrees are governed by a power law.
        :rtype : None
        """
        self.banks.sort(key=lambda _bank: _bank.balance.assets)
        degrees = [math.floor(Statistics.draw_from_powerlaw(POWERLAW_EXPONENT_OUT_DEGREE, 1.0) + 0.6748)
                   for degree in xrange(0, len(self.banks))]
        for i in xrange(0, len(degrees)):
            if degrees[i] > MAX_K_OUT:
                degrees[i] = MAX_K_OUT
        degrees.sort()
        for n in xrange(0, len(self.banks)):
            self.banks[n].out_degree = degrees[n]

    def allocate_loans(self):
        """
        Allocates loans between banks. These form the network that holds the interbank system together.
        If supply is bigger than demand of chosen banks, higher demand banks are chosen.
        If that does not work, internal balance sheet composition is adjusted to equate supply to demand available.
        Loan size is determined in such a manner to ensure non-zero and non-trivial loans.
        :rtype : None
        """
        id_list = copy.deepcopy(self.id_to_bank.keys())
        length = len(id_list)
        print 'Starting loan allocation...'

        for bank_id in id_list:
            # Progress indicator:
            progress = 100 * (float(id_list.index(bank_id)) / length)
            print 'Loan allocation: {0}%\r'.format(round(progress, 2)),

            # allocation of bank-bank connections:
            self.banks.sort(key=lambda _bank: _bank.borrowing_demand)  # sort banks from low to to high demand
            bank = self.id_to_bank[bank_id]                            # reference to bank
            borrowers_indices = range(NUMBER_OF_BANKS - 1, NUMBER_OF_BANKS - int(bank.out_degree) - 1, -1)
            borrowers_ids = [self.banks[index].bank_id for index in borrowers_indices]
            if bank_id in borrowers_ids:
                place = borrowers_indices.index(bank_id)
                borrowers_indices[place] = self.banks[NUMBER_OF_BANKS - int(bank.out_degree) - 2]  # no loans to self

            if self.aggregate_demand(borrowers_indices) < bank.lending_supply:
                 # adjust balance sheet composition:
                 old_lending_fraction = bank.lending_supply / bank.balance.assets
                 new_lending_fraction = self.aggregate_demand(borrowers_indices) / bank.balance.assets
                 addition = (old_lending_fraction - new_lending_fraction) / 2
                 bank.balance.cash_fraction += addition
                 bank.balance.consumer_loans_fraction += addition

            # create interbank loans:
            for borrower_index in borrowers_indices:<commit_msg>fixes<commit_after>"""Controller.py
Contains simulation controller class, and inherent simulation logic.
"""

import copy
import math
import networkx as nx
import pickle
from statistics import Statistics
from bank import Bank
from settings import *


class Controller(object):
    """
    Controls simulation, allocation, and the underlying network.
    """
    def __init__(self, import_network=False, export_network=False, build_network=False):
        self.banks = []  # contains all bank objects
        self.id_to_bank = dict()  # reference map of id's to all banks.
        if build_network:
            self.build_network()
        if export_network:
            self.export_network_to_disk(NETWORK_EXPORT_PATH)
        if import_network:
            self.import_network_from_disk(NETWORK_EXPORT_PATH)  # imports network created earlier by the program
        self.defaulted_banks = 0.0
        #self.trigger(self.banks[50].bank_id) # initial trigger (example)
        #self.export_network_to_disk(FAILED_NETWORK_EXPORT_PATH)  # save network after triggering defaults.
        
    def build_network(self):
        """
        Creates the loan network between banks.
        :rtype : None

        """
        self.create_banks()
        self.allocate_degrees()
        self.allocate_loans()

    def create_banks(self):
        """
        Creates bank objects, stores them in self.banks, and registers them in a self.bank_to_id.
        A part of all banks follows a Pareto distribution, all others a log-normal distribution, in asset size.
        All created banks are registered in the dictionary self.id_to_bank.
        :rtype : None
        """
        for n in xrange(0, NUMBER_OF_BANKS_LOGNORMAL):
            bank_size = Statistics.generate_lognormal_number(LOGNORMAL_MEAN, LOGNOMRAL_STDEV)
            bank = Bank(bank_size)
            self.banks.append(bank)
            self.id_to_bank[id(bank)] = bank

        for n in xrange(0, NUMBER_OF_BANKS_PARETO):
            bank_size = Statistics.generate_pareto_number(PARETO_SCALE, PARETO_SHAPE)
            bank = Bank(bank_size)
            self.banks.append(bank)
            self.id_to_bank[id(bank)] = bank

    def allocate_degrees(self):
        """
        First sort banks by asset size, then match them with sorted degree list.
        Node degrees are governed by a power law.
        :rtype : None
        """
        self.banks.sort(key=lambda _bank: _bank.balance.assets)
        degrees = [math.floor(Statistics.draw_from_powerlaw(POWERLAW_EXPONENT_OUT_DEGREE, 1.0) + 0.6748)
                   for degree in xrange(0, len(self.banks))]
        for i in xrange(0, len(degrees)):
            if degrees[i] > MAX_K_OUT:
                degrees[i] = MAX_K_OUT
        degrees.sort()
        for n in xrange(0, len(self.banks)):
            self.banks[n].out_degree = degrees[n]

    def allocate_loans(self):
        """
        Allocates loans between banks. These form the network that holds the interbank system together.
        If supply is bigger than demand of chosen banks, higher demand banks are chosen.
        If that does not work, internal balance sheet composition is adjusted to equate supply to demand available.
        Loan size is determined in such a manner to ensure non-zero and non-trivial loans.
        :rtype : None
        """
        id_list = copy.deepcopy(self.id_to_bank.keys())
        length = len(id_list)
        print 'Starting loan allocation...'

        for bank_id in id_list:
            # Progress indicator:
            progress = 100 * (float(id_list.index(bank_id)) / length)
            print 'Loan allocation: {0}%\r'.format(round(progress, 2)),

            # allocation of bank-bank connections:
            self.banks.sort(key=lambda _bank: _bank.borrowing_demand)  # sort banks from low to to high demand
            bank = self.id_to_bank[bank_id]                            # reference to bank
            borrowers_indices = range(NUMBER_OF_BANKS - 1, NUMBER_OF_BANKS - int(bank.out_degree) - 1, -1)
            borrowers_ids = [self.banks[index].bank_id for index in borrowers_indices]
            if bank_id in borrowers_ids:
                # no loans to self
                place = borrowers_ids.index(bank_id)
                borrowers_indices[place] = NUMBER_OF_BANKS - int(bank.out_degree) - 2

            if self.aggregate_demand(borrowers_indices) < bank.lending_supply:
                 # adjust balance sheet composition:
                 old_lending_fraction = bank.lending_supply / bank.balance.assets
                 new_lending_fraction = self.aggregate_demand(borrowers_indices) / bank.balance.assets
                 addition = (old_lending_fraction - new_lending_fraction) / 2
                 bank.balance.cash_fraction += addition
                 bank.balance.consumer_loans_fraction += addition

            # create interbank loans:
            for borrower_index in borrowers_indices:
{'fraction_non_alphanumeric': 0.07807292192707807, 'fraction_numerical': 0.005251994748005252, 'mean_word_length': 2.378369157284203, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1720303', 'n_tokens_mistral': 2894, 'n_tokens_neox': 2776, 'n_words': 767}