Example #1
0
    def test_generate_fault_source_model_complex(self):
        '''
        Tests the function to turn fault model into mtkSimpleFault or
        mtkComplexFault
        '''
        self.fault = mtkActiveFault(
            '001',
            'A Fault',
            self.complex_fault,
            [(5.0, 1.0)],
            0.,
            None,
            aseismic=0.,
            msr_sigma=[(0.0, 1.0)],
            neotectonic_fault=None,
            scale_rel=[(WC1994(), 1.0)],
            aspect_ratio=1.0,
            shear_modulus=[(30., 1.0)],
            disp_length_ratio=[(1.25E-5, 1.0)])
        # Define simple placeholder MFD
        rec_models = [IncrementalMFD(5.0, 0.1, 1.0 * np.ones(10)),
                      IncrementalMFD(5.0, 0.1, 2.0 * np.ones(10))]
        self.fault.mfd = (rec_models, [0.5, 0.5], [WC1994(), WC1994()])

        # Run model
        source_model, weights = self.fault.generate_fault_source_model()
        self.assertEqual(len(source_model), 2)
        self.assertTrue(isinstance(source_model[0], mtkComplexFaultSource))
        for iloc, model in enumerate(source_model):
            self.assertEqual(model.id, '001')
            self.assertTrue(isinstance(model.mfd, EvenlyDiscretizedMFD))
            np.testing.assert_array_almost_equal(model.mfd.occurrence_rates,
                                                 rec_models[iloc].occur_rates)
            self.assertAlmostEqual(weights[iloc], 0.5)
Example #2
0
 def test_generate_config_set_with_bad_weights(self):
     '''
     Tests that a valueError is raised when the config weights do not sum
     to 1.0
     '''
     self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
         [(5., 1.0)], 0., None)
     bad_config = [{'MFD_spacing': 0.1,
                    'Maximum_Magnitude': None,
                    'Minimum_Magnitude': 5.0,
                     'Model_Name': 'AndersonLucoArbitrary',
                     'Model_Weight': 0.5,
                     'Type': 'First',
                     'b_value': [0.8, 0.05]},
                    {'MFD_spacing': 0.1,
                     'Maximum_Magnitude': None,
                     'Maximum_Magnitude_Uncertainty': None,
                     'Minimum_Magnitude': 5.0,
                     'Model_Name': 'YoungsCoppersmithExponential',
                     'Model_Weight': 0.3,
                     'b_value': [0.8, 0.05]}]
     with self.assertRaises(ValueError) as ae:
         self.fault.generate_config_set(bad_config)
     self.assertEqual(ae.exception.message,
                      'MFD config weights do not sum to 1.0 for fault 001')
Example #3
0
 def _build_mock_recurrence_branches(self):
     '''
     Given the mock branches return information necessary to define a
     collapse model
     '''
     # Build test data
     mags = COLLAPSE_DATA[0, :-1]
     weights = COLLAPSE_DATA[1:-1, -1]
     rates = COLLAPSE_DATA[1:-1:, :-1]
     expected_rate = COLLAPSE_DATA[-1, :-1]
     test_fault = mtkActiveFault(
         '001',
         'A Fault',
         self.simple_fault,
         [(5.0, 1.0)],
         0.,
         None)
     test_fault.mfd_models = []
     for (iloc, weight) in enumerate(weights):
         idx = rates[iloc, :] > 0
         model = RecurrenceBranch(None, None, None, None, None,
                                  weight=weight)
         model.recurrence = IncrementalMFD(np.min(rates[iloc, idx]), 0.1,
                                           rates[iloc, idx])
         model.magnitudes = mags[idx]
         test_fault.mfd_models.append(model)
     return test_fault, expected_rate, np.min(mags), np.max(mags), weights
Example #4
0
    def test_select_catalogue_rrup(self):
        """
        Tests catalogue selection with Joyner-Boore distance
        """
        self.fault = mtkActiveFault(
            '001',
            'A Fault',
            self.simple_fault,
            [(5., 0.5), (7., 0.5)],
            0.,
            None,
            msr_sigma=[(-1.5, 0.15), (0., 0.7), (1.5, 0.15)])

        cat1 = Catalogue()
        cat1.data = {"eventID": ["001", "002", "003", "004"],
                     "longitude": np.array([30.1, 30.1, 30.5, 31.5]),
                     "latitude": np.array([30.0, 30.25, 30.4, 30.5]),
                     "depth": np.array([5.0, 250.0, 10.0, 10.0])}
        selector = CatalogueSelector(cat1)
        # Select within 50 km of the fault
        self.fault.select_catalogue(selector, 50.0,
                                    distance_metric="rupture")
        np.testing.assert_array_almost_equal(
            self.fault.catalogue.data["longitude"],
            np.array([30.1, 30.5]))
        np.testing.assert_array_almost_equal(
            self.fault.catalogue.data["latitude"],
            np.array([30.0, 30.4]))
        np.testing.assert_array_almost_equal(
            self.fault.catalogue.data["depth"],
            np.array([5.0, 10.0]))
Example #5
0
 def test_generate_branching_index(self):
     '''
     Simple test to check that a correct branching index is raised
     Slip - 2 values
     MSR - 1 value 
     Shear Modulus - 2 value
     DLR - 1 value
     MSR_Sigma - 3 Values
     Config - 1 value
     '''
     self.fault = mtkActiveFault('001',
                                 'A Fault',
                                 self.simple_fault, [(5., 0.5), (7., 0.5)],
                                 0.,
                                 None,
                                 msr_sigma=[(-1.5, 0.15), (0., 0.7),
                                            (1.5, 0.15)],
                                 neotectonic_fault=None,
                                 scale_rel=[(WC1994(), 1.0)],
                                 aspect_ratio=1.0,
                                 shear_modulus=[(28., 0.5), (30., 0.5)],
                                 disp_length_ratio=[(1.25E-5, 1.0)])
     # Set with only one config - no data input
     self.fault.generate_config_set({})
     expected_result = np.array(
         [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 2, 0],
          [0, 0, 1, 0, 0, 0], [0, 0, 1, 0, 1, 0], [0, 0, 1, 0, 2, 0],
          [1, 0, 0, 0, 0, 0], [1, 0, 0, 0, 1, 0], [1, 0, 0, 0, 2, 0],
          [1, 0, 1, 0, 0, 0], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 2, 0]],
         dtype=int)
     branch_index, number_branches = \
         self.fault._generate_branching_index()
     np.testing.assert_array_equal(branch_index, expected_result)
     self.assertEqual(number_branches, 12)
Example #6
0
    def test_generate_recurrence_models_bad_input(self):
        '''
        Tests to ensure correct errors are raised when input is bad
        '''
        self.fault = mtkActiveFault(
            '001',
            'A Fault',
            self.simple_fault,
            [(5., 0.5), (7., 0.5)],
            0.,
            None,
            msr_sigma=[(-1.5, 0.15), (0., 0.7), (1.5, 0.15)])

        # Test 1 - Non-iterable config
        bad_config = None
        with self.assertRaises(ValueError) as ae:
            self.fault.generate_recurrence_models(bad_config)
            self.assertEqual(ae.exception.message,
                'MFD configuration missing or incorrectly formatted')
        # Test 2 - Collapse is required but no msr set!
        with self.assertRaises(ValueError) as ae:
            self.fault.generate_recurrence_models(collapse=True)
            self.assertEqual(ae.exception.message,
                'Collapsing logic tree branches requires input '
                'of a single msr for rendering sources')
Example #7
0
 def test_generate_config_set_as_dict(self):
     '''
     Tests the function to generate a configuration tuple list from a
     list of multiple config dicts
     '''
     self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
         [(5., 1.0)], 0., None)
     good_config = [{'MFD_spacing': 0.1,
                    'Maximum_Magnitude': None,
                    'Minimum_Magnitude': 5.0,
                     'Model_Name': 'AndersonLucoArbitrary',
                     'Model_Weight': 0.7,
                     'Model_Type': 'First',
                     'b_value': [0.8, 0.05]},
                    {'MFD_spacing': 0.1,
                     'Maximum_Magnitude': None,
                     'Maximum_Magnitude_Uncertainty': None,
                     'Minimum_Magnitude': 5.0,
                     'Model_Name': 'YoungsCoppersmithExp',
                     'Model_Weight': 0.3,
                     'b_value': [0.8, 0.05]}]
     self.fault.generate_config_set(good_config)
     self.assertTrue(isinstance(self.fault.config, list))
     self.assertEqual(len(self.fault.config), 2)
     self.assertDictEqual(self.fault.config[0][0], good_config[0])
     self.assertDictEqual(self.fault.config[1][0], good_config[1])
     self.assertAlmostEqual(self.fault.config[0][1], 0.7)
     self.assertAlmostEqual(self.fault.config[1][1], 0.3)
Example #8
0
 def test_mtk_active_fault_not_bad_input_geometry(self):
     '''
     Tests the instantiation with a bad geometry input - should raise error
     '''
     with self.assertRaises(IOError) as ioe:
         self.fault = mtkActiveFault('001', 'A Fault', 'Nonsense',
                                 self.slip, 0., 'Active Shallow Crust')
     self.assertEqual(ioe.exception.message, 'Geometry must be instance '
                      'of hmtk.faults.fault_geometries.BaseFaultGeometry')
Example #9
0
 def test_generate_config_set_bad_input(self):
     '''
     Tests that valueError is raised when the config is not input as either
     a list or dict
     '''
     self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
         [(5., 1.0)], 0., None)
     with self.assertRaises(ValueError) as ae:
         self.fault.generate_config_set(None)
     self.assertEqual(ae.exception.message,
                      'MFD config must be input as dictionary or list!')
Example #10
0
 def test_mtk_active_fault_not_bad_input_slip(self):
     '''
     Tests the instantiation with slip wieghts not equal to 1.0 -
     should raise value error
     '''
     with self.assertRaises(ValueError) as ae:
         self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
                                     [(5.0, 0.5), (7.0, 0.4)], 0.,
                                     'Active Shallow Crust')
     self.assertEqual(ae.exception.message,
                      'Slip rate weightings must sum to 1.0')
Example #11
0
    def test_mtk_active_fault_instantiation(self):
        '''
        Tests core instantiation of mtkActiveFault Class
        '''

        self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
                                    self.slip, 0., 'Active Shallow Crust',
                                    aseismic=0.5)
        self.assertListEqual([(0., 1.0)], self.fault.msr_sigma)
        self.assertAlmostEqual(self.fault.area, 1200.)
        self.assertListEqual([(5.0, 1.0)], self.fault.slip)
Example #12
0
    def read_file(self, mesh_spacing = 1.0):
        '''
        Reads the file and returns an instance of the FaultSource class
        :param float mesh_spacing:
            Fault mesh spacing (km)
        '''
        
        # Process the tectonic regionalisation
        tectonic_reg = self.process_tectonic_regionalisation()
        
        model = mtkActiveFaultModel(self.data['Fault_Model_ID'], 
                           self.data['Fault_Model_Name'])
        for fault in self.data['Fault_Model']:
            fault_geometry = self.read_fault_geometry(fault['Fault_Geometry'],
                                                      mesh_spacing)
            if fault['Shear_Modulus']:
                fault['Shear_Modulus'] = weight_list_to_tuple(
                    fault['Shear_Modulus'], '%s Shear Modulus' % fault['ID'])
            
            if fault['Displacement_Length_Ratio']:
                fault['Displacement_Length_Ratio'] = weight_list_to_tuple(
                    fault['Displacement_Length_Ratio'], 
                    '%s Displacement to Length Ratio' % fault['ID'])

            
            fault_source = mtkActiveFault(
                fault['ID'],
                fault['Fault_Name'],
                fault_geometry,
                weight_list_to_tuple(fault['Slip'], '%s - Slip' % fault['ID']),
                float(fault['Rake']),
                fault['Tectonic_Region'],
                float(fault['Aseismic']),
                weight_list_to_tuple(
                    fault['Scaling_Relation_Sigma'], 
                    '%s Scaling_Relation_Sigma' % fault['ID']),
                neotectonic_fault=None,
                scale_rel=get_scaling_relation_tuple(
                    fault['Magnitude_Scaling_Relation']),
                aspect_ratio=fault['Aspect_Ratio'],
                shear_modulus=fault['Shear_Modulus'],
                disp_length_ratio=fault['Displacement_Length_Ratio'])

            if tectonic_reg:
                fault_source.get_tectonic_regionalisation(
                    tectonic_reg,
                    fault['Tectonic_Region'])
            assert isinstance(fault['MFD_Model'], list)
            fault_source.generate_config_set(fault['MFD_Model'])
            model.faults.append(fault_source)
            
        return model, tectonic_reg
Example #13
0
    def test_recurrence_collapse_branches(self):
        '''
        Tests the recurrence model generated by collapsing branches
        '''

        self.fault = mtkActiveFault('001',
                                    'A Fault',
                                    self.simple_fault, [(5., 0.5), (7., 0.5)],
                                    0.,
                                    None,
                                    aseismic=0.,
                                    msr_sigma=[(0.0, 1.0)],
                                    neotectonic_fault=None,
                                    scale_rel=[(WC1994(), 1.0)],
                                    aspect_ratio=1.0,
                                    shear_modulus=[(30., 1.0)],
                                    disp_length_ratio=[(1.25E-5, 1.0)])
        mfd_config = [{
            'MFD_spacing': 0.1,
            'Maximum_Magnitude': None,
            'Minimum_Magnitude': 5.0,
            'Model_Name': 'AndersonLucoArbitrary',
            'Model_Weight': 0.7,
            'Model_Type': 'First',
            'b_value': [0.8, 0.05]
        }, {
            'MFD_spacing': 0.1,
            'Maximum_Magnitude': None,
            'Maximum_Magnitude_Uncertainty': None,
            'Minimum_Magnitude': 5.0,
            'Model_Name': 'YoungsCoppersmithExponential',
            'Model_Weight': 0.3,
            'b_value': [0.8, 0.05]
        }]

        # Enumerated branches should have four models
        self.fault.generate_recurrence_models(collapse=True,
                                              rendered_msr=WC1994(),
                                              config=mfd_config)

        expected_rates = 0.
        expected_weights = np.array([0.35, 0.15, 0.35, 0.15])
        for iloc in range(0, 4):
            expected_rates = expected_rates + (expected_weights[iloc] *
                                               10.**FAULT_RATE_DATA[iloc, :])
        np.testing.assert_array_almost_equal(
            np.log10(self.fault.mfd[0][0].occur_rates),
            np.log10(expected_rates))
Example #14
0
    def test_recurrence_collapse_branches(self):
        '''
        Tests the recurrence model generated by collapsing branches
        '''

        self.fault = mtkActiveFault(
            '001',
            'A Fault',
            self.simple_fault,
            [(5., 0.5), (7., 0.5)],
            0.,
            None,
            aseismic=0.,
            msr_sigma=[(0.0, 1.0)],
            neotectonic_fault=None,
            scale_rel=[(WC1994(), 1.0)],
            aspect_ratio=1.0,
            shear_modulus=[(30., 1.0)],
            disp_length_ratio=[(1.25E-5, 1.0)])
        mfd_config = [{'MFD_spacing': 0.1,
                       'Maximum_Magnitude': None,
                       'Minimum_Magnitude': 5.0,
                        'Model_Name': 'AndersonLucoArbitrary',
                        'Model_Weight': 0.7,
                        'Model_Type': 'First',
                        'b_value': [0.8, 0.05]},
                       {'MFD_spacing': 0.1,
                        'Maximum_Magnitude': None,
                        'Maximum_Magnitude_Uncertainty': None,
                        'Minimum_Magnitude': 5.0,
                        'Model_Name': 'YoungsCoppersmithExponential',
                        'Model_Weight': 0.3,
                        'b_value': [0.8, 0.05]}]

        # Enumerated branches should have four models
        self.fault.generate_recurrence_models(collapse=True,
                                              rendered_msr=WC1994(),
                                              config=mfd_config)

        expected_rates = 0.
        expected_weights = np.array([0.35, 0.15, 0.35, 0.15])
        for iloc in range(0, 4):
            expected_rates = expected_rates + (expected_weights[iloc] *
                                               10. ** FAULT_RATE_DATA[iloc, :])
        np.testing.assert_array_almost_equal(
            np.log10(self.fault.mfd[0][0].occur_rates),
            np.log10(expected_rates))
Example #15
0
    def test_generate_recurrence_models_no_collapse(self):
        '''
        Tests the generate recurrence models option without collapsing
        branches: simple example with two slip rates and two mfd configurations
        '''
        self.fault = mtkActiveFault(
            '001',
            'A Fault',
            self.simple_fault,
            [(5., 0.5), (7., 0.5)],
            0.,
            None,
            aseismic=0.,
            msr_sigma=[(0.0, 1.0)],
            neotectonic_fault=None,
            scale_rel=[(WC1994(), 1.0)],
            aspect_ratio=1.0,
            shear_modulus=[(30., 1.0)],
            disp_length_ratio=[(1.25E-5, 1.0)])
        mfd_config = [{'MFD_spacing': 0.1,
                       'Maximum_Magnitude': None,
                       'Minimum_Magnitude': 5.0,
                        'Model_Name': 'AndersonLucoArbitrary',
                        'Model_Weight': 0.7,
                        'Model_Type': 'First',
                        'b_value': [0.8, 0.05]},
                       {'MFD_spacing': 0.1,
                        'Maximum_Magnitude': None,
                        'Maximum_Magnitude_Uncertainty': None,
                        'Minimum_Magnitude': 5.0,
                        'Model_Name': 'YoungsCoppersmithExponential',
                        'Model_Weight': 0.3,
                        'b_value': [0.8, 0.05]}]

        # Enumerates branches should have four models
        self.fault.generate_recurrence_models(config=mfd_config)
        mfds = self.fault.mfd[0]
        weights = self.fault.mfd[1]
        np.testing.assert_array_almost_equal(
            weights,
            np.array([0.35, 0.15, 0.35, 0.15]))
        for (iloc, occur) in enumerate(mfds):
            np.testing.assert_array_almost_equal(
                np.log10(occur.occur_rates),
                FAULT_RATE_DATA[iloc, :])
Example #16
0
 def test_generate_config_set_as_dict(self):
     '''
     Tests the function to generate a configuration tuple list from a
     single config dict
     '''
     self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
         [(5., 1.0)], 0., None)
     good_config = {'MFD_spacing': 0.1,
                    'Maximum_Magnitude': None,
                    'Minimum_Magnitude': 5.0,
                    'Model_Name': 'AndersonLucoArbitrary',
                    'Model_Weight': 1.0,
                    'Model_Type': 'First',
                    'b_value': [0.8, 0.05]}
     self.fault.generate_config_set(good_config)
     self.assertTrue(isinstance(self.fault.config, list))
     self.assertDictEqual(self.fault.config[0][0], good_config)
     self.assertAlmostEqual(self.fault.config[0][1], 1.0)
Example #17
0
    def test_get_tectonic_regionalisation_missing_case(self):
        '''
         Test case when no region is defined - should raise error
        '''
        # Set up regionalistion
        region_dict = [{'Code': '001', 'Name': 'Active Shallow Crust'}]
        tect_reg = TectonicRegionalisation()
        tect_reg.populate_regions(region_dict)

        self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
                                    self.slip, 0., None)

        with self.assertRaises(ValueError) as ae:
            self.fault.get_tectonic_regionalisation(tect_reg, None)

        self.assertEqual(ae.exception.message,
                        'Tectonic region classification missing or '
                        'not defined in regionalisation')
Example #18
0
    def test_get_tectonic_regionalisation(self):
        '''
        Tests the retreival of tectonic regionalisation information
        '''
        # Set up regionalistion
        region_dict = [{'Code': '001', 'Name': 'Active Shallow Crust'}]
        tect_reg = TectonicRegionalisation()
        tect_reg.populate_regions(region_dict)

        # Test successful case
        self.fault = mtkActiveFault('001', 'A Fault', self.simple_fault,
                                    self.slip, 0., None)
        self.fault.get_tectonic_regionalisation(tect_reg,
                                                'Active Shallow Crust')
        self.assertEqual(self.fault.trt, 'Active Shallow Crust')
        # Should take default values
        self.assertListEqual(self.fault.shear_modulus, [(30., 1.0)])
        self.assertListEqual(self.fault.disp_length_ratio, [(1.25E-5, 1.0)])
        self.assertTrue(isinstance(self.fault.msr[0][0], WC1994))
        self.assertAlmostEqual(self.fault.msr[0][1], 1.0)
Example #19
0
 def test_generate_branching_index(self):
     '''
     Simple test to check that a correct branching index is raised
     Slip - 2 values
     MSR - 1 value
     Shear Modulus - 2 value
     DLR - 1 value
     MSR_Sigma - 3 Values
     Config - 1 value
     '''
     self.fault = mtkActiveFault(
         '001',
         'A Fault',
         self.simple_fault,
         [(5., 0.5), (7., 0.5)],
         0.,
         None,
         msr_sigma=[(-1.5, 0.15), (0., 0.7), (1.5, 0.15)],
         neotectonic_fault=None,
         scale_rel=[(WC1994(), 1.0)],
         aspect_ratio=1.0,
         shear_modulus=[(28., 0.5), (30., 0.5)],
         disp_length_ratio=[(1.25E-5, 1.0)])
     # Set with only one config - no data input
     self.fault.generate_config_set({})
     expected_result = np.array([[0, 0, 0, 0, 0, 0],
                                 [0, 0, 0, 0, 1, 0],
                                 [0, 0, 0, 0, 2, 0],
                                 [0, 0, 1, 0, 0, 0],
                                 [0, 0, 1, 0, 1, 0],
                                 [0, 0, 1, 0, 2, 0],
                                 [1, 0, 0, 0, 0, 0],
                                 [1, 0, 0, 0, 1, 0],
                                 [1, 0, 0, 0, 2, 0],
                                 [1, 0, 1, 0, 0, 0],
                                 [1, 0, 1, 0, 1, 0],
                                 [1, 0, 1, 0, 2, 0]], dtype=int)
     branch_index, number_branches = \
         self.fault._generate_branching_index()
     np.testing.assert_array_equal(branch_index, expected_result)
     self.assertEqual(number_branches, 12)
Example #20
0
    def test_build_fault_model(self):
        '''
        Tests the constuction of a fault model with two faults (1 simple, 
        1 complex) each with two mfd rates - should produce four sources
        '''
        self.model = mtkActiveFaultModel('001', 'A Fault Model', faults=[])

        x0 = Point(30., 30., 0.)
        x1 = x0.point_at(30., 0., 30.)
        x2 = x1.point_at(30., 0., 60.)
        # Total length is 60 km
        trace = Line([x0, x1, x2])
        simple_fault = SimpleFaultGeometry(trace, 90., 0., 20.)
        # Creates a trace ~60 km long made of 3 points
        upper_edge = Line([x0, x1, x2])
        lower_edge = Line([
            x0.point_at(40., 20., 130.),
            x1.point_at(42., 25., 130.),
            x2.point_at(41., 22., 130.)
        ])
        complex_fault = ComplexFaultGeometry([upper_edge, lower_edge], 2.0)
        config = [{
            'MFD_spacing': 0.1,
            'Maximum_Magnitude': 7.0,
            'Maximum_Uncertainty': None,
            'Model_Name': 'Characteristic',
            'Model_Weight': 0.5,
            'Sigma': 0.1,
            'Lower_Bound': -1.,
            'Upper_Bound': 1.
        }, {
            'MFD_spacing': 0.1,
            'Maximum_Magnitude': 7.5,
            'Maximum_Uncertainty': None,
            'Model_Name': 'Characteristic',
            'Model_Weight': 0.5,
            'Sigma': 0.1,
            'Lower_Bound': -1.,
            'Upper_Bound': 1.
        }]
        fault1 = mtkActiveFault('001',
                                'Simple Fault 1',
                                simple_fault, [(10.0, 1.0)],
                                -90.,
                                None,
                                aspect_ratio=1.0,
                                scale_rel=[(WC1994(), 1.0)],
                                shear_modulus=[(30.0, 1.0)],
                                disp_length_ratio=[(1E-5, 1.0)])
        fault1.generate_config_set(config)
        fault2 = mtkActiveFault('002',
                                'Complex Fault 1',
                                complex_fault, [(10.0, 1.0)],
                                -90.,
                                None,
                                aspect_ratio=1.0,
                                scale_rel=[(WC1994(), 1.0)],
                                shear_modulus=[(30.0, 1.0)],
                                disp_length_ratio=[(1E-5, 1.0)])
        fault2.generate_config_set(config)
        self.model.faults = [fault1, fault2]

        # Generate source model
        self.model.build_fault_model()
        self.assertEqual(len(self.model.source_model.sources), 4)
        # First source should be an instance of a mtkSimpleFaultSource
        model1 = self.model.source_model.sources[0]
        self.assertTrue(isinstance(model1, mtkSimpleFaultSource))
        self.assertEqual(model1.id, '001_1')
        self.assertAlmostEqual(model1.mfd.min_mag, 6.9)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model1.mfd.occurrence_rates)),
            np.array([-2.95320041, -2.54583708, -2.953200413]))

        # Second source should be an instance of a mtkSimpleFaultSource
        model2 = self.model.source_model.sources[1]
        self.assertTrue(isinstance(model2, mtkSimpleFaultSource))
        self.assertEqual(model2.id, '001_2')
        self.assertAlmostEqual(model2.mfd.min_mag, 7.4)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model2.mfd.occurrence_rates)),
            np.array([-3.70320041, -3.29583708, -3.70320041]))

        # Third source should be an instance of a mtkComplexFaultSource
        model3 = self.model.source_model.sources[2]
        self.assertTrue(isinstance(model3, mtkComplexFaultSource))
        self.assertEqual(model3.id, '002_1')
        self.assertAlmostEqual(model3.mfd.min_mag, 6.9)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model3.mfd.occurrence_rates)),
            np.array([-2.59033387, -2.18297054, -2.59033387]))

        # Fourth source should be an instance of a mtkComplexFaultSource
        model4 = self.model.source_model.sources[3]
        self.assertTrue(isinstance(model4, mtkComplexFaultSource))
        self.assertEqual(model4.id, '002_2')
        self.assertAlmostEqual(model4.mfd.min_mag, 7.4)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model4.mfd.occurrence_rates)),
            np.array([-3.34033387, -2.93297054, -3.34033387]))
    def test_build_fault_model(self):
        # Tests the constuction of a fault model with two faults (1 simple,
        # 1 complex) each with two mfd rates - should produce four sources
        self.model = mtkActiveFaultModel('001', 'A Fault Model', faults=[])
        x0 = Point(30., 30., 0.)
        x1 = x0.point_at(30., 0., 30.)
        x2 = x1.point_at(30., 0., 60.)
        # Total length is 60 km
        trace = Line([x0, x1, x2])
        simple_fault = SimpleFaultGeometry(trace, 90., 0., 20.)
        # Creates a trace ~60 km long made of 3 points
        upper_edge = Line([x0, x1, x2])
        lower_edge = Line(
            [x0.point_at(40., 20., 130.),
             x1.point_at(42., 25., 130.),
             x2.point_at(41., 22., 130.)])
        complex_fault = ComplexFaultGeometry([upper_edge, lower_edge], 2.0)
        config = [{'MFD_spacing': 0.1,
                   'Maximum_Magnitude': 7.0,
                   'Maximum_Uncertainty': None,
                   'Model_Name': 'Characteristic',
                   'Model_Weight': 0.5,
                   'Sigma': 0.1,
                   'Lower_Bound': -1.,
                   'Upper_Bound': 1.},
                  {'MFD_spacing': 0.1,
                   'Maximum_Magnitude': 7.5,
                   'Maximum_Uncertainty': None,
                   'Model_Name': 'Characteristic',
                   'Model_Weight': 0.5,
                   'Sigma': 0.1,
                   'Lower_Bound': -1.,
                   'Upper_Bound': 1.}]
        fault1 = mtkActiveFault('001', 'Simple Fault 1', simple_fault,
                                [(10.0, 1.0)], -90., None,
                                aspect_ratio=1.0,
                                scale_rel=[(WC1994(), 1.0)],
                                shear_modulus=[(30.0, 1.0)],
                                disp_length_ratio=[(1E-5, 1.0)])
        fault1.generate_config_set(config)
        fault2 = mtkActiveFault('002', 'Complex Fault 1', complex_fault,
                                [(10.0, 1.0)], -90., None,
                                aspect_ratio=1.0,
                                scale_rel=[(WC1994(), 1.0)],
                                shear_modulus=[(30.0, 1.0)],
                                disp_length_ratio=[(1E-5, 1.0)])
        fault2.generate_config_set(config)
        self.model.faults = [fault1, fault2]

        # Generate source model
        self.model.build_fault_model()
        self.assertEqual(len(self.model.source_model.sources), 4)
        # First source should be an instance of a mtkSimpleFaultSource
        model1 = self.model.source_model.sources[0]
        self.assertTrue(isinstance(model1, mtkSimpleFaultSource))
        self.assertEqual(model1.id, '001_1')
        self.assertAlmostEqual(model1.mfd.min_mag, 6.9)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model1.mfd.occurrence_rates)),
            np.array([-2.95320041, -2.54583708, -2.953200413]))

        # Second source should be an instance of a mtkSimpleFaultSource
        model2 = self.model.source_model.sources[1]
        self.assertTrue(isinstance(model2, mtkSimpleFaultSource))
        self.assertEqual(model2.id, '001_2')
        self.assertAlmostEqual(model2.mfd.min_mag, 7.4)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model2.mfd.occurrence_rates)),
            np.array([-3.70320041, -3.29583708, -3.70320041]))

        # Third source should be an instance of a mtkComplexFaultSource
        model3 = self.model.source_model.sources[2]
        self.assertTrue(isinstance(model3, mtkComplexFaultSource))
        self.assertEqual(model3.id, '002_1')
        self.assertAlmostEqual(model3.mfd.min_mag, 6.9)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model3.mfd.occurrence_rates)),
            np.array([-2.59033387, -2.18297054, -2.59033387]))

        # Fourth source should be an instance of a mtkComplexFaultSource
        model4 = self.model.source_model.sources[3]
        self.assertTrue(isinstance(model4, mtkComplexFaultSource))
        self.assertEqual(model4.id, '002_2')
        self.assertAlmostEqual(model4.mfd.min_mag, 7.4)
        np.testing.assert_array_almost_equal(
            np.log10(np.array(model4.mfd.occurrence_rates)),
            np.array([-3.34033387, -2.93297054, -3.34033387]))