def test_symbols_to_sympy(self): result = fsic.tools.symbols_to_sympy(fsic.parse_model('Y = C + G')) expected = { sympy.Symbol('Y'): sympy.Eq(sympy.Symbol('Y'), sympy.sympify('C + G')), } self.assertEqual(result, expected)
def test_linker_to_dataframes_core(self): Submodel = fsic.build_model(fsic.parse_model('Y = C + I + G + X - M')) model = fsic.BaseLinker({ 'A': Submodel(range(1990, 2005 + 1)), 'B': Submodel(range(1990, 2005 + 1)), 'C': Submodel(range(1990, 2005 + 1)), }, name='test') model.add_variable('D', 0.0) results = model.to_dataframes() pd.testing.assert_frame_equal(results['test'], pd.DataFrame({'D': 0.0, 'status': '-', 'iterations': -1, }, index=range(1990, 2005 + 1))) expected = pd.DataFrame({x: 0.0 for x in 'YCIGXM'}, index=range(1990, 2005 + 1)) expected['status'] = '-' expected['iterations'] = -1 for name, submodel in model.submodels.items(): with self.subTest(submodel=name): pd.testing.assert_frame_equal(results[name], expected)
def test_symbols_to_sympy_singleton_and_imaginary(self): # Also test (forced) conversion of 'I' to a SymPy Symbol (rather # than an imaginary number) result = fsic.tools.symbols_to_sympy(fsic.parse_model('I = S')) expected = { sympy.Symbol('I'): sympy.Eq(sympy.Symbol('I'), sympy.Symbol('S')), } self.assertEqual(result, expected)
def test_symbols_to_sympy_singleton(self): # Test (forced) conversion of 'S' to a SymPy Symbol (rather than a # Singleton) result = fsic.tools.symbols_to_sympy(fsic.parse_model('S = YD - C')) expected = { sympy.Symbol('S'): sympy.Eq(sympy.Symbol('S'), sympy.sympify('YD - C')), } self.assertEqual(result, expected)
class TestNetworkXFunctions(unittest.TestCase): SYMBOLS = fsic.parse_model('Y = C + G') def test_symbols_to_graph(self): result = fsic.tools.symbols_to_graph(self.SYMBOLS) expected = nx.DiGraph() expected.add_nodes_from(['Y[t]'], equation='Y[t] = C[t] + G[t]') expected.add_edge('C[t]', 'Y[t]') expected.add_edge('G[t]', 'Y[t]') self.assertEqual(result.nodes, expected.nodes) self.assertEqual(result.edges, expected.edges)
class TestCompile(FortranTestWrapper, unittest.TestCase): TEST_MODULE_NAME = 'fsic_test_fortran_testcompile' # Define a large number of variables of the same type (here, arbitrarily, # exogenous variables) to check that index numbers in the resulting Fortran # code are line-wrapped correctly SCRIPT = ('RESULT = A + B + C + D + E + F + G + H + I + J + ' 'K + L + M + N + O + P + Q + R + S + T + ' 'U + V + W + X + Y + Z') SYMBOLS = fsic.parse_model(SCRIPT) def test_continuation_lines(self): # Test line wrapping for long lines # No code here: Check that the code successfully compiles during # `setUp()` pass
class TestPandasFunctions(unittest.TestCase): SYMBOLS = fsic.parse_model('Y = C + G') MODEL = fsic.build_model(SYMBOLS) def test_symbols_to_dataframe(self): result = fsic.tools.symbols_to_dataframe(self.SYMBOLS) expected = pd.DataFrame({ 'name': ['Y', 'C', 'G'], 'type': [fsic.parser.Type.ENDOGENOUS, fsic.parser.Type.EXOGENOUS, fsic.parser.Type.EXOGENOUS], 'lags': 0, 'leads': 0, 'equation': ['Y[t] = C[t] + G[t]', None, None], 'code': ['self._Y[t] = self._C[t] + self._G[t]', None, None], }) pd.testing.assert_frame_equal(result, expected) def test_dataframe_to_symbols(self): # Check by way of a roundtrip: symbols -> DataFrame -> symbols result = fsic.tools.symbols_to_dataframe(self.SYMBOLS) expected = pd.DataFrame({ 'name': ['Y', 'C', 'G'], 'type': [fsic.parser.Type.ENDOGENOUS, fsic.parser.Type.EXOGENOUS, fsic.parser.Type.EXOGENOUS], 'lags': 0, 'leads': 0, 'equation': ['Y[t] = C[t] + G[t]', None, None], 'code': ['self._Y[t] = self._C[t] + self._G[t]', None, None], }) # Initial (i.e. pre-)check only pd.testing.assert_frame_equal(result, expected) # Check by value self.assertEqual(fsic.tools.dataframe_to_symbols(result), self.SYMBOLS) self.assertEqual(fsic.tools.dataframe_to_symbols(expected), self.SYMBOLS) # Check by string representation for before, after in zip(self.SYMBOLS, fsic.tools.dataframe_to_symbols(result)): with self.subTest(symbol=before): self.assertEqual(str(before), str(after)) self.assertEqual(repr(before), repr(after)) def test_model_to_dataframe(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) result = fsic.tools.model_to_dataframe(model) expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'status': '-', 'iterations': -1 }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_no_status(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) expected = fsic.tools.model_to_dataframe(model).drop('status', axis='columns') result = fsic.tools.model_to_dataframe(model, status=False) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_no_iterations(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) expected = fsic.tools.model_to_dataframe(model).drop('iterations', axis='columns') result = fsic.tools.model_to_dataframe(model, iterations=False) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_no_status_or_iterations(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) expected = fsic.tools.model_to_dataframe(model).drop(['status', 'iterations'], axis='columns') result = fsic.tools.model_to_dataframe(model, status=False, iterations=False) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_additional_variables(self): # Check that extending the model with extra variables carries through # to the results DataFrame (including preserving variable types) model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) model.add_variable('I', 0, dtype=int) model.add_variable('J', 0) model.add_variable('K', 0, dtype=float) model.add_variable('L', False, dtype=bool) # Check list of names is now changed self.assertEqual(model.names, model.NAMES + ['I', 'J', 'K', 'L']) result = fsic.tools.model_to_dataframe(model) expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'I': 0, # int 'J': 0.0, # float 'K': 0.0, # float (forced) 'L': False, # bool 'status': '-', 'iterations': -1 }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_core(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) result = model.to_dataframe() expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'status': '-', 'iterations': -1 }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_core_no_status(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) result = model.to_dataframe(status=False) expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'iterations': -1 }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_core_no_iterations(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) result = model.to_dataframe(iterations=False) expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'status': '-', }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_core_no_status_or_iterations(self): model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) result = model.to_dataframe(status=False, iterations=False) expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_model_to_dataframe_additional_variables_core(self): # Check that extending the model with extra variables carries through # to the results DataFrame (including preserving variable types) model = self.MODEL(range(5)) # Check list of names is unchanged self.assertEqual(model.names, model.NAMES) model.add_variable('I', 0, dtype=int) model.add_variable('J', 0) model.add_variable('K', 0, dtype=float) model.add_variable('L', False, dtype=bool) # Check list of names is now changed self.assertEqual(model.names, model.NAMES + ['I', 'J', 'K', 'L']) result = model.to_dataframe() expected = pd.DataFrame({ 'Y': 0.0, 'C': 0.0, 'G': 0.0, 'I': 0, # int 'J': 0.0, # float 'K': 0.0, # float (forced) 'L': False, # bool 'status': '-', 'iterations': -1 }, index=range(5)) pd.testing.assert_frame_equal(result, expected) def test_linker_to_dataframes(self): Submodel = fsic.build_model(fsic.parse_model('Y = C + I + G + X - M')) model = fsic.BaseLinker({ 'A': Submodel(range(1990, 2005 + 1)), 'B': Submodel(range(1990, 2005 + 1)), 'C': Submodel(range(1990, 2005 + 1)), }, name='test') model.add_variable('D', 0.0) results = fsic.tools.linker_to_dataframes(model) pd.testing.assert_frame_equal(results['test'], pd.DataFrame({'D': 0.0, 'status': '-', 'iterations': -1, }, index=range(1990, 2005 + 1))) expected = pd.DataFrame({x: 0.0 for x in 'YCIGXM'}, index=range(1990, 2005 + 1)) expected['status'] = '-' expected['iterations'] = -1 for name, submodel in model.submodels.items(): with self.subTest(submodel=name): pd.testing.assert_frame_equal(results[name], expected) def test_linker_to_dataframes_core(self): Submodel = fsic.build_model(fsic.parse_model('Y = C + I + G + X - M')) model = fsic.BaseLinker({ 'A': Submodel(range(1990, 2005 + 1)), 'B': Submodel(range(1990, 2005 + 1)), 'C': Submodel(range(1990, 2005 + 1)), }, name='test') model.add_variable('D', 0.0) results = model.to_dataframes() pd.testing.assert_frame_equal(results['test'], pd.DataFrame({'D': 0.0, 'status': '-', 'iterations': -1, }, index=range(1990, 2005 + 1))) expected = pd.DataFrame({x: 0.0 for x in 'YCIGXM'}, index=range(1990, 2005 + 1)) expected['status'] = '-' expected['iterations'] = -1 for name, submodel in model.submodels.items(): with self.subTest(submodel=name): pd.testing.assert_frame_equal(results[name], expected)
if use_aliases: df = df.rename(columns={v: k for k, v in self.aliases.items()}) return df script = ''' C = {alpha_1} * YD + {alpha_2} * H[-1] YD = Y - T Y = C + G T = {theta} * Y H = H[-1] + YD - C ''' symbols = fsic.parse_model(script) SIM = fsic.build_model(symbols) class SIMAlias(AliasMixin, SIM): ALIASES = { 'GDP': 'Y', 'mpc_income': 'alpha_1', 'mpc_wealth': 'alpha_2', 'income_tax_rate': 'theta', } if __name__ == '__main__': # Run Model *SIM* as usual ----------------------------------------------- model = SIM(range(1945, 2010 + 1), alpha_1=0.6, alpha_2=0.4)
class TestBuildAndSolve(FortranTestWrapper, unittest.TestCase): TEST_MODULE_NAME = 'fsic_test_fortran_testbuildandsolve' # Definition of a stripped-down Model *SIM* from Chapter 3 of Godley and # Lavoie (2007) SCRIPT = ''' C = {alpha_1} * YD + {alpha_2} * H[-1] YD = Y - T Y = C + G T = {theta} * Y H = H[-1] + YD - C ''' SYMBOLS = fsic.parse_model(SCRIPT) def setUp(self): super().setUp() PythonClass = fsic.build_model(self.SYMBOLS) FortranClass = self.Model # Instantiate a Python and a corresponding Fortran instance of the # model self.model_python = PythonClass(range(100), alpha_1=0.6, alpha_2=0.4) self.model_fortran = FortranClass(range(100), alpha_1=0.6, alpha_2=0.4) def test_initialisation_error(self): # Check that the Fortran class catches a missing (unlinked) Fortran # module with self.assertRaises(fsic.fortran.InitialisationError): fsic.fortran.FortranEngine(range(10)) def test_evaluate(self): # Check Python- and Fortran-based evaluate functions generate the same # results # Python self.model_python.G = 20 self.model_python.theta = 0.2 for i in self.model_python.span[3:10]: for _ in range(100): self.model_python._evaluate(i) for _ in range(100): self.model_python._evaluate(-10) # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 for i in self.model_fortran.span[3:10]: for _ in range(100): self.model_fortran._evaluate(i) for _ in range(100): self.model_fortran._evaluate(-10) # Comparison self.assertEqual(self.model_python.values.shape, self.model_fortran.values.shape) self.assertTrue(np.allclose(self.model_python.values, self.model_fortran.values)) def test_evaluate_index_errors(self): # Check model instances correctly throw errors if period (`t`) # parameters are out of bounds with self.assertRaises(IndexError): self.model_python._evaluate(100) with self.assertRaises(IndexError): self.model_fortran._evaluate(100) with self.assertRaises(IndexError): self.model_python._evaluate(-100) with self.assertRaises(IndexError): self.model_fortran._evaluate(-100) def test_solve_t_max_iter(self): # Check that the Fortran code returns the correct number of iterations # if it reaches `max_iter` # (In Fortran, a loop that completes seems to leave the counter 1 # higher than the loop limit. This isn't the case if the loop exits # early.) # Python self.model_python.G = 20 self.model_python.theta = 0.2 self.model_python.solve(max_iter=2, failures='ignore') self.assertTrue(np.all(self.model_python.iterations[1:] == 2)) # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 self.model_fortran.solve(max_iter=2, failures='ignore') self.assertTrue(np.all(self.model_fortran.iterations[1:] == 2)) def test_solve_t_errors_ignore(self): # Check that the `errors='ignore'` solve option ignores NaNs properly # Python self.model_python.G = 20 self.model_python.theta = 0.2 self.model_python.C[2] = np.NaN # Solution should halt because of the pre-existing NaN with self.assertRaises(fsic.exceptions.SolutionError): self.model_python.solve() self.model_python.solve(errors='ignore') # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 self.model_fortran.C[2] = np.NaN # Solution should halt because of the pre-existing NaN with self.assertRaises(fsic.exceptions.SolutionError): self.model_fortran.solve() self.model_fortran.solve(errors='ignore') # Comparison self.assertEqual(self.model_python.values.shape, self.model_fortran.values.shape) self.assertTrue(np.allclose(self.model_python.values, self.model_fortran.values)) def test_solve(self): # Check Python- and Fortran-based solve functions generate the same # results # Python self.model_python.G = 20 self.model_python.theta = 0.2 self.model_python.solve() # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 self.model_fortran.solve() # Comparison self.assertEqual(self.model_python.values.shape, self.model_fortran.values.shape) self.assertTrue(np.allclose(self.model_python.values, self.model_fortran.values)) def test_solve_offset(self): # Check Python- and Fortran-based solve functions generate the same # results with the `offset` keyword argument # Python self.model_python.G = 20 self.model_python.theta = 0.2 self.model_python.solve(offset=-1) # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 self.model_fortran.solve(offset=-1) # Comparison self.assertEqual(self.model_python.values.shape, self.model_fortran.values.shape) self.assertTrue(np.allclose(self.model_python.values, self.model_fortran.values)) def test_solve_max_iter_non_convergence_error(self): # Check Python- and Fortran-based solve functions generate the same # error if the model fails to solve # Python self.model_python.G = 20 self.model_python.theta = 0.2 with self.assertRaises(fsic.exceptions.NonConvergenceError): self.model_python.solve(max_iter=5) # Fortran self.model_fortran.G = 20 self.model_fortran.theta = 0.2 with self.assertRaises(fsic.exceptions.NonConvergenceError): self.model_fortran.solve(max_iter=5) # Comparison self.assertEqual(self.model_python.values.shape, self.model_fortran.values.shape) self.assertTrue(np.allclose(self.model_python.values, self.model_fortran.values))
from tqdm import tqdm import fsic # Define the example model script = ''' C = {alpha_1} * YD + {alpha_2} * H[-1] YD = Y - T Y = C + G T = {theta} * Y H = H[-1] + YD - C ''' # Parse the script and generate a class definition SIM = fsic.build_model(fsic.parse_model(script)) if __name__ == '__main__': # Initialise a model instance (which will be copied in each example) base = SIM(range(1945, 2010 + 1), alpha_1=0.6, alpha_2=0.4, G=20, theta=0.2) # ------------------------------------------------------------------------- # 1. Call `iter_periods()` directly each time, wrapping the iterator with # `tqdm` (two ways shown below) # 1a. Wrap `iter_periods()` print('1a. Wrap `iter_periods()`:')