示例#1
0
    def test_unit_handling(self):
        """
        Tests unit handling with a simple model that calculates the area of a rectangle as the
        product of two lengths.

        In this case the input lengths are provided in centimeters and meters.
        Tests whether the input units are properly coerced into canonical types.
        Tests whether the output units are properly set.
        Tests whether the model returns as predicted.
        Returns:
            None
        """
        L = Symbol('l', ['L'], ['L'], units=[1.0, [['centimeter', 1.0]]], shape=[1])
        A = Symbol('a', ['A'], ['A'], units=[1.0, [['centimeter', 2.0]]], shape=[1])
        get_area_config = {
            'name': 'area',
            # 'connections': [{'inputs': ['l1', 'l2'], 'outputs': ['a']}],
            'equations': ['a = l1 * l2'],
            # 'unit_map': {'l1': "cm", "l2": "cm", 'a': "cm^2"}
            'symbol_property_map': {"a": A, "l1": L, "l2": L}
        }
        model = EquationModel(**get_area_config)
        out = model.evaluate({'l1': QuantityFactory.create_quantity(L, 1, 'meter'),
                              'l2': QuantityFactory.create_quantity(L, 2)}, allow_failure=False)

        self.assertTrue(math.isclose(out['a'].magnitude, 200.0))
        self.assertTrue(out['a'].units == A.units)
示例#2
0
    def test_model_returns_complex(self):
        # This tests model failure with scalar complex.
        # Quantity class has other more thorough tests.

        A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1)
        B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=1)
        for sym in (B, A):
            Registry("symbols")[sym] = sym
            Registry("units")[sym] = sym.units
        get_config = {
            'name': 'add_complex_value',
            # 'connections': [{'inputs': ['b'], 'outputs': ['a']}],
            'equations': ['a = b + 1j'],
            # 'unit_map': {'a': "dimensionless", 'a': "dimensionless"}
            'variable_symbol_map': {
                "a": A,
                "b": B
            }
        }
        model = EquationModel(**get_config)
        out = model.evaluate({'b': QuantityFactory.create_quantity(B, 5)},
                             allow_failure=True)
        self.assertFalse(out['successful'])
        self.assertEqual(out['message'],
                         'Evaluation returned invalid values (complex)')

        out = model.evaluate({'b': QuantityFactory.create_quantity(B, 5j)},
                             allow_failure=True)
        self.assertTrue(out['successful'])
        self.assertTrue(np.isclose(out['a'].magnitude, 6j))
示例#3
0
    def test(self, inputs, outputs):
        """
        Runs a test of the model to determine whether its operation
        is consistent with the specified inputs and outputs

        Args:
            inputs (dict): set of input names to values
            outputs (dict): set of output names to values

        Returns (bool): True if test succeeds
        """
        evaluate_inputs = self.map_symbols_to_properties(inputs)
        unit_map_as_properties = self.map_symbols_to_properties(self.unit_map)
        evaluate_inputs = {
            s: QuantityFactory.create_quantity(s, v,
                                               unit_map_as_properties.get(s))
            for s, v in evaluate_inputs.items()}
        evaluate_outputs = self.evaluate(evaluate_inputs, allow_failure=False)
        evaluate_outputs = self.map_properties_to_symbols(evaluate_outputs)
        errmsg = "{} model test failed on ".format(self.name) + "{}\n"
        errmsg += "{}(test data) = {}\n"#.format(k, known_output)
        errmsg += "{}(model output) = {}"#.format(k, plug_in_output)
        for k, known_output in outputs.items():
            symbol = self.symbol_property_map[k]
            units = self.unit_map.get(k)
            known_quantity = QuantityFactory.create_quantity(symbol, known_output, units)
            evaluate_output = evaluate_outputs[k]
            if not known_quantity.has_eq_value_to(evaluate_output):
                errmsg = errmsg.format("evaluate", k, evaluate_output,
                                       k, known_quantity)
                raise ModelEvaluationError(errmsg)

        return True
示例#4
0
 def test_add_default_quantities(self):
     material = Material(add_default_quantities=True)
     self.assertEqual(list(material['temperature'])[0],
                      QuantityFactory.create_quantity("temperature", 300,
                                                      provenance=ProvenanceElement(model='default')))
     self.assertEqual(list(material['relative_permeability'])[0],
                      QuantityFactory.create_quantity("relative_permeability", 1,
                      provenance=ProvenanceElement(model='default')))
示例#5
0
 def setUp(self):
     # Create some test properties and a few base objects
     self.q1 = QuantityFactory.create_quantity(DEFAULT_SYMBOLS['bulk_modulus'],
                                               ureg.Quantity.from_tuple([200, [['gigapascals', 1]]]))
     self.q2 = QuantityFactory.create_quantity(DEFAULT_SYMBOLS['shear_modulus'],
                                               ureg.Quantity.from_tuple([100, [['gigapascals', 1]]]))
     self.q3 = QuantityFactory.create_quantity(DEFAULT_SYMBOLS['bulk_modulus'],
                                               ureg.Quantity.from_tuple([300, [['gigapascals', 1]]]))
     self.material = Material()
     self.graph = Graph()
示例#6
0
 def test_get_weight(self):
     q1 = QuantityFactory.create_quantity("band_gap", 3.2)
     wt = get_weight(q1)
     self.assertEqual(wt, 1)
     p2 = ProvenanceElement(model="model_2", inputs=[q1])
     q2 = QuantityFactory.create_quantity("refractive_index", 4, provenance=p2)
     wt2 = get_weight(q2, {"model_2": 0.5})
     self.assertEqual(wt2, 0.5)
     p3 = ProvenanceElement(model="model_3", inputs=[q2])
     q3 = QuantityFactory.create_quantity("bulk_modulus", 100, provenance=p3)
     wt3 = get_weight(q3, {"model_3": 0.25, "model_2": 0.5})
     self.assertEqual(wt3, 0.125)
示例#7
0
 def setUpClass(cls):
     add_builtin_symbols_to_registry()
     # Create some test properties and a few base objects
     cls.q1 = QuantityFactory.create_quantity(
         Registry("symbols")['bulk_modulus'],
         ureg.Quantity.from_tuple([200, [['gigapascals', 1]]]))
     cls.q2 = QuantityFactory.create_quantity(
         Registry("symbols")['shear_modulus'],
         ureg.Quantity.from_tuple([100, [['gigapascals', 1]]]))
     cls.q3 = QuantityFactory.create_quantity(
         Registry("symbols")['bulk_modulus'],
         ureg.Quantity.from_tuple([300, [['gigapascals', 1]]]))
     cls.material = None
     cls.graph = Graph()
示例#8
0
    def get_materials_for_mpids(self, mpids, filter_null_properties=True):
        """
        Retrieve a list of Materials from the materials
        Project for a given list of Materials Project IDs.

        Args:
            mpids: a list of Materials Project IDs

        Returns:

        """

        materials_properties = self.get_properties_for_mpids(
            mpids, filter_null_properties=filter_null_properties)
        materials = []

        for material_properties in materials_properties:
            material = Material()
            for property_name, property_value in material_properties.items():
                provenance = ProvenanceElement(source='Materials Project')
                quantity = QuantityFactory.create_quantity(
                    self.mapping[property_name],
                    property_value,
                    provenance=provenance)
                material.add_quantity(quantity)
            materials.append(material)

        return materials
示例#9
0
    def evaluate(input_rows, data, aggregate):

        quantities = [
            QuantityFactory.create_quantity(
                symbol_type=ROW_IDX_TO_SYMBOL_NAME[idx],
                value=ureg.parse_expression(row['Editable Value']))
            for idx, row in enumerate(input_rows) if row['Editable Value']
        ]

        if data and len(data) > 0:
            quantities += json.loads(data, cls=MontyDecoder).values()

        if not quantities:
            raise PreventUpdate

        material = Material()

        for quantity in quantities:
            material.add_quantity(quantity)

        graph = Graph()
        output_material = graph.evaluate(material)

        if aggregate:
            output_quantities = output_material.get_aggregated_quantities(
            ).values()
        else:
            output_quantities = output_material.get_quantities()

        output_rows = [{
            'Property': quantity.symbol.display_names[0],
            'Value': quantity.pretty_string(sigfigs=3)
        } for quantity in output_quantities]

        output_table = dt.DataTable(id='output-table',
                                    rows=output_rows,
                                    editable=False)

        # TODO: clean up

        input_quantity_names = [q.symbol.name for q in quantities]
        derived_quantity_names = set(
            [q.symbol.name for q in output_quantities]) - \
                                 set(input_quantity_names)
        material_graph_data = graph_conversion(
            graph.get_networkx_graph(),
            nodes_to_highlight_green=input_quantity_names,
            nodes_to_highlight_yellow=list(derived_quantity_names))
        options = AESTHETICS['global_options']
        options['edges']['color'] = '#000000'
        output_graph = html.Div(GraphComponent(id='material-graph',
                                               graph=material_graph_data,
                                               options=options),
                                style={
                                    'width': '100%',
                                    'height': '400px'
                                })

        return [output_graph, html.Br(), output_table]
示例#10
0
 def test_get_sse(self):
     mats = [Material([QuantityFactory.create_quantity("band_gap", n)]) for n in range(1, 5)]
     benchmarks = [{"band_gap": 1.1*n} for n in range(1, 5)]
     err = get_sse(mats, benchmarks)
     test_val = sum([0.01*n**2 for n in range(1, 5)])
     self.assertAlmostEqual(err, test_val)
     # Big dataset
     err = get_sse(self.evaluated, self.benchmarks)
     self.assertAlmostEqual(err, 173.5710251)
示例#11
0
 def setUp(self):
     path = os.path.join(TEST_DIR, "fitting_test_data.csv")
     test_data = pd.read_csv(path)
     graph = Graph()
     materials = [Material([QuantityFactory.create_quantity("band_gap", bg)])
                  for bg in test_data['band_gap']]
     self.evaluated = [graph.evaluate(mat) for mat in materials]
     self.benchmarks = [{"refractive_index": n}
                        for n in test_data['refractive_index']]
示例#12
0
 def setUpClass(cls):
     add_builtin_models_to_registry()
     # This is the visible light dataset used in the propnet paper
     path = os.path.join(TEST_DATA_DIR, "vis_bg_ri_data.csv")
     test_data = pd.read_csv(path)
     graph = Graph()
     materials = [
         Material([QuantityFactory.create_quantity("band_gap", bg)])
         for bg in test_data['Band Gap']
     ]
     cls.evaluated = [graph.evaluate(mat) for mat in materials]
     cls.benchmarks = [{
         "refractive_index": n
     } for n in test_data['Refractive Index']]
示例#13
0
    def to_quantity(self, lookup=None):
        """
        Converts the current StorageQuantity to the appropriate BaseQuantity-derived
        object.

        Verifies that the object's provenance has complete values, which would be
        missing if read in from a JSON-serialized dictionary. If values are found
        to be missing, a lookup function/dictionary is required to reconstitute the data.

        Lookup dictionaries should be keyed by internal ID, and have values which are
        dictionaries with the fields shown below. Lookup functions should take one argument,
        internal ID, and return a dictionary with the fields below.

        Lookup return value construction:
            value: (id) value of the quantity
            units: (str) units of quantity, None if no units
            uncertainty: (int, float, complex) uncertainty value, None if no uncertainty

        Args:
            lookup: (dict or function) lookup container for missing values. Not required
                if self.needs_lookup() is False

        Returns: (BaseQuantity) converted BaseQuantity-derived object

        """
        if self.needs_lookup() and not lookup:
            raise ValueError(
                "StorageQuantity cannot be converted to BaseQuantity-derived"
                " object because it is missing values in provenance inputs. "
                "Please provide a lookup dictionary or function with the "
                "following keys: {}".format(self.get_missing_keys()))

        provenance_in = \
            self._provenance.to_provenance_element(lookup=lookup) \
            if self._provenance else None

        out = QuantityFactory.create_quantity(symbol_type=self._symbol_type,
                                              value=self._value,
                                              units=self._units,
                                              tags=self._tags,
                                              provenance=provenance_in,
                                              uncertainty=self._uncertainty)

        out._internal_id = self._internal_id
        return out
示例#14
0
def aggregate_quantities(quantities, model_score_dict=None):
    """
    Simple method for aggregating a set of quantities

    Args:
        quantities:
        model_score_dict:

    Returns:

    """
    symbol = next(iter(quantities)).symbol
    if not all([q.symbol == symbol for q in quantities]):
        raise ValueError("Quantities passed to aggregate must be same symbol")
    weights = [get_weight(q, model_score_dict) for q in quantities]
    result_value = sum(
        [w * q.value for w, q in zip(weights, quantities)]) / sum(weights)
    return QuantityFactory.create_quantity(symbol, result_value)
示例#15
0
    def test_model_returns_nan(self):
        # This tests model failure with scalar nan.
        # Quantity class has other more thorough tests.

        A = Symbol('a', ['A'], ['A'], units='dimensionless', shape=1)
        B = Symbol('b', ['B'], ['B'], units='dimensionless', shape=1)
        get_config = {
            'name': 'equality',
            # 'connections': [{'inputs': ['b'], 'outputs': ['a']}],
            'equations': ['a = b'],
            # 'unit_map': {'a': "dimensionless", 'a': "dimensionless"}
            'symbol_property_map': {"a": A, "b": B}
        }
        model = EquationModel(**get_config)
        out = model.evaluate({'b': QuantityFactory.create_quantity(B, float('nan'))},
                             allow_failure=True)
        self.assertFalse(out['successful'])
        self.assertEqual(out['message'], 'Evaluation returned invalid values (NaN)')
示例#16
0
    def get_materials_for_mpids(self, mpids, filter_null_values=True):
        """
        Retrieve a list of Materials from the materials
        Project for a given list of Materials Project IDs.

        Args:
            mpids: a list of Materials Project IDs

        Returns:

        """

        materials_quantities = self.get_quantities_for_mpids(
            mpids,
            filter_null_values=filter_null_values,
            include_date_created=True)
        materials = []

        for material_quantities in materials_quantities:
            material = Material()
            try:
                date_created = material_quantities.pop('created_at')
            except KeyError:
                date_created = None
            for symbol_name, value in material_quantities.items():
                provenance = ProvenanceElement(
                    source={
                        'source': 'Materials Project',
                        'source_key': material_quantities.get(
                            'material_id', None),
                        'date_created': date_created
                    })
                quantity = QuantityFactory.create_quantity(
                    self.mapping[symbol_name],
                    value,
                    units=Registry("units").get(self.mapping[symbol_name],
                                                None),
                    provenance=provenance)
                material.add_quantity(quantity)
            materials.append(material)

        return materials
示例#17
0
    def transform_properties_to_material(self, material_data):
        """
        Produces a propnet Material object from a dictionary of AFLOW materials data.

        Args:
            material_data (dict): AFLOW materials data, keyed by AFLOW keyword, as
                Python native types, not as strings as they are stored in AFLOW.

        Returns:
            propnet.core.materials.Material: propnet material containing the AFLOW data
        """
        qs = []
        auid = material_data.get('auid')
        date_created = material_data.get('aflowlib_date')
        if date_created:
            date, tz = date_created.rsplit("GMT", 1)
            tz = "GMT{:+05d}".format(int(tz) * 100)
            date_object = datetime.strptime(date + tz, "%Y%m%d_%H:%M:%S_%Z%z")
            date_created = date_object.strftime("%Y-%m-%d %H:%M:%S")

        for prop, value in material_data.items():
            if value is not None and prop in self.mapping:
                provenance = ProvenanceElement(
                    source={'source': 'AFLOW',
                            'source_key': auid,
                            'date_created': date_created}
                )

                if prop in self.transform_func:
                    value = self.transform_func[prop](value)
                if value is None:
                    continue
                q = QuantityFactory.create_quantity(
                    self.mapping.get(prop),
                    value,
                    units=self.unit_map.get(prop), provenance=provenance
                )
                qs.append(q)

        return Material(qs)
示例#18
0
    def process(self, item):
        if self.graph_parallel and not self.allow_child_process and \
                current_process().name != "MainProcess":
            logger.warning(
                "It appears derive_quantities() is running "
                "in a child process, possibly in a parallelized "
                "Runner.\nThis is not recommended and will deteriorate "
                "performance.")
        # Define quantities corresponding to materials doc fields
        # Attach quantities to materials
        item = MontyDecoder().process_decoded(item)
        logger.info("Populating material for %s", item['task_id'])
        material = Material()

        if 'created_at' in item.keys():
            date_created = item['created_at']
        else:
            date_created = None

        provenance = ProvenanceElement(
            source={
                "source": self.source_name,
                "source_key": item['task_id'],
                "date_created": date_created
            })

        for mkey, property_name in self.materials_symbol_map.items():
            value = pydash.get(item, mkey)
            if value:
                material.add_quantity(
                    QuantityFactory.create_quantity(
                        property_name,
                        value,
                        units=Registry("units").get(property_name, None),
                        provenance=provenance))

        # Add custom things, e. g. computed entry
        computed_entry = get_entry(item)
        if computed_entry:
            material.add_quantity(
                QuantityFactory.create_quantity("computed_entry",
                                                computed_entry,
                                                provenance=provenance))
        else:
            logger.info("Unable to create computed entry for {}".format(
                item['task_id']))
        material.add_quantity(
            QuantityFactory.create_quantity("external_identifier_mp",
                                            item['task_id'],
                                            provenance=provenance))

        input_quantities = material.symbol_quantities_dict

        # Use graph to generate expanded quantity pool
        logger.info("Evaluating graph for %s", item['task_id'])

        new_material = self._graph_evaluator.evaluate(
            material, timeout=self.graph_timeout)

        # Format document and return
        logger.info("Creating doc for %s", item['task_id'])
        # Gives the initial inputs that were used to derive properties of a
        # certain material.

        doc = {
            "inputs": [
                StorageQuantity.from_quantity(q)
                for q in chain.from_iterable(input_quantities.values())
            ]
        }

        for symbol, quantities in new_material.symbol_quantities_dict.items():
            # If no new quantities of a given symbol were derived (i.e. if the initial
            # input quantity/ies is/are the only one/s listed in the new material) then don't add
            # that quantity to the propnet entry document as a derived quantity.
            if len(quantities) == len(input_quantities[symbol]):
                continue
            sub_doc = {}
            try:
                # Write out all quantities as dicts including the
                # internal ID for provenance tracing
                qs = [
                    jsanitize(StorageQuantity.from_quantity(q), strict=True)
                    for q in quantities
                ]
            except AttributeError as ex:
                # Check to see if this is an error caused by an object
                # that is not JSON serializable
                msg = ex.args[0]
                if "object has no attribute 'as_dict'" in msg:
                    # Write error to db and logger
                    errmsg = "Quantity of Symbol '{}' is not ".format(symbol.name) + \
                        "JSON serializable. Cannot write quantities to database!"
                    logger.error(errmsg)
                    sub_doc['error'] = errmsg
                    qs = []
                else:
                    # If not, re-raise the error
                    raise ex
            sub_doc['quantities'] = qs
            doc[symbol.name] = sub_doc

        aggregated_quantities = new_material.get_aggregated_quantities()

        for symbol, quantity in aggregated_quantities.items():
            if symbol.name not in doc:
                # No new quantities were derived
                continue
            # Store mean and std dev for aggregated quantities
            sub_doc = {
                "mean": unumpy.nominal_values(quantity.value).tolist(),
                "std_dev": unumpy.std_devs(quantity.value).tolist(),
                "units":
                quantity.units.format_babel() if quantity.units else None,
                "title": quantity.symbol.display_names[0]
            }
            # Symbol Name -> Sub_Document, listing all Quantities of that type.
            doc[symbol.name].update(sub_doc)

        doc.update({
            "task_id": item["task_id"],
            "pretty_formula": item.get("pretty_formula"),
            "deprecated": item.get("deprecated", False)
        })

        if self.include_sandboxed:
            doc.update({'sbxn': item.get("sbxn", [])})

        return jsanitize(doc, strict=True)
示例#19
0
    def evaluate(input_rows, data, aggregate):

        quantities = []

        for idx, row in enumerate(input_rows):
            if row['Editable Value']:
                try:
                    value = ureg.parse_expression(row['Editable Value'])
                    units = Registry("units").get(ROW_IDX_TO_SYMBOL_NAME[idx])
                    value.ito(units)
                except Exception:
                    # Someone put an invalid value in the table
                    # TODO: Make error known to the user
                    raise PreventUpdate
                q = QuantityFactory.create_quantity(
                    symbol_type=ROW_IDX_TO_SYMBOL_NAME[idx], value=value)
                quantities.append(q)

        if data and len(data) > 0:
            quantities += json.loads(data, cls=MontyDecoder).values()

        if not quantities:
            raise PreventUpdate

        material = Material()

        for quantity in quantities:
            material.add_quantity(quantity)

        output_material = graph_evaluator.evaluate(material, timeout=5)

        if aggregate:
            aggregated_quantities = output_material.get_aggregated_quantities()
            non_aggregatable_quantities = [
                v for v in output_material.get_quantities()
                if v.symbol not in aggregated_quantities
            ]
            output_quantities = list(
                aggregated_quantities.values()) + non_aggregatable_quantities
        else:
            output_quantities = output_material.get_quantities()

        output_rows = [{
            'Property': quantity.symbol.display_names[0],
            'Value': quantity.pretty_string(sigfigs=3)
        } for quantity in output_quantities]

        output_table = dt.DataTable(id='output-table',
                                    data=output_rows,
                                    columns=[{
                                        'id': val,
                                        'name': val
                                    } for val in ('Property', 'Value')],
                                    editable=False,
                                    **DATA_TABLE_STYLE)

        # TODO: clean up

        input_quantity_names = [q.symbol for q in quantities]
        derived_quantity_names = \
            set([q.symbol for q in output_quantities]) - \
            set(input_quantity_names)

        models_evaluated = set(
            output_q.provenance.model
            for output_q in output_material.get_quantities())
        models_evaluated = [
            Registry("models").get(m) for m in models_evaluated
            if Registry("models").get(m) is not None
        ]

        material_graph_data = graph_conversion(
            propnet_nx_graph,
            derivation_pathway={
                'inputs': input_quantity_names,
                'outputs': list(derived_quantity_names),
                'models': models_evaluated
            })

        output_graph = html.Div(children=[
            dcc.Checklist(id='material-graph-options',
                          options=[{
                              'label': 'Show models',
                              'value': 'show_models'
                          }, {
                              'label': 'Show properties',
                              'value': 'show_properties'
                          }],
                          value=['show_properties'],
                          labelStyle={'display': 'inline-block'}),
            Cytoscape(id='material-graph',
                      elements=material_graph_data,
                      stylesheet=GRAPH_STYLESHEET,
                      layout=GRAPH_LAYOUT_CONFIG,
                      **GRAPH_SETTINGS['full_view'])
        ])

        return [output_graph, html.Br(), output_table]
示例#20
0
    def setUp(self):
        # Inspiration was taken from the GraphTest class
        # I tried to construct the dictionaries for comparison
        # without writing out every one explicity by reusing
        # information where it was applicable.
        # If this is too unreadable, can change to writing it
        # out explicity in a JSON file and importing it. Would
        # still need to replace some fields dynamically.
        symbols = StorageTest.generate_symbols()

        self.custom_syms_as_dicts = {
            k: {'@module': 'propnet.core.symbols',
                '@class': 'Symbol',
                'name': k,
                'display_names': [k],
                'display_symbols': [k],
                'units': (1, ()),
                'shape': 1,
                'object_type': None,
                'comment': None,
                'category': 'property',
                'constraint': None,
                'default_value': None} for k in ['A', 'B', 'C']
        }
        self.custom_syms_as_dicts['C'].update(
            {"units": None,
             "shape": None,
             "object_type": "str",
             "category": "object"})

        self.custom_symbols_json = copy.deepcopy(self.custom_syms_as_dicts)
        for k in ['A', 'B']:
            self.custom_symbols_json[k]['units'] = [1, []]

        a = [QuantityFactory.create_quantity(symbols['A'], 19),
             QuantityFactory.create_quantity(symbols['A'], 23)]
        b = [QuantityFactory.create_quantity(symbols['B'], 38,
                                             provenance=ProvenanceElement(model='model1',
                                                                          inputs=[a[0]])),
             QuantityFactory.create_quantity(symbols['B'], 46,
                                             provenance=ProvenanceElement(model='model1',
                                                                          inputs=[a[1]]))]
        self.quantities_custom_symbol = {"A": a,
                                         "B": b}

        self.sq_custom_sym_as_dicts = {
            k: [{'@module': 'propnet.dbtools.storage',
                 '@class': 'StorageQuantity',
                 'internal_id': vv._internal_id,
                 'data_type': 'NumQuantity',
                 'symbol_type': symbols[k],
                 'value': vv.magnitude,
                 'units': 'dimensionless',
                 'provenance': ProvenanceStore.from_provenance_element(vv.provenance),
                 'tags': [],
                 'uncertainty': None} for vv in v] for k, v in self.quantities_custom_symbol.items()
        }

        provenances_json = {
            "A": [{'@module': 'propnet.dbtools.storage',
                   '@class': 'ProvenanceStore',
                   'model': None,
                   'inputs': None,
                   'source': aa.provenance.source} for aa in a]}
        provenances_json['B'] = [
            {'@module': 'propnet.dbtools.storage',
             '@class': 'ProvenanceStore',
             'model': 'model1',
             'inputs': [{'@module': 'propnet.dbtools.storage',
                         '@class': 'ProvenanceStoreQuantity',
                         'data_type': 'NumQuantity',
                         'symbol_type': self.custom_symbols_json['A'],
                         'internal_id': q.provenance.inputs[0]._internal_id,
                         'tags': [],
                         'provenance': p}],
             'source': q.provenance.source} for q, p in zip(b, provenances_json['A'])]

        self.sq_custom_sym_json = copy.deepcopy(self.sq_custom_sym_as_dicts)
        for sym in ['A', 'B']:
            for q, p in zip(self.sq_custom_sym_json[sym], provenances_json[sym]):
                q['symbol_type'] = self.custom_symbols_json[sym]
                q['provenance'] = p

        band_gaps = [QuantityFactory.create_quantity('band_gap', 3.3, 'eV'),
                     QuantityFactory.create_quantity('band_gap', 2.1, 'eV')]

        bg_ri_model = DEFAULT_MODEL_DICT['band_gap_refractive_index_moss']
        refractive_indices = [bg_ri_model.evaluate({"Eg": bg}).pop('refractive_index') for bg in band_gaps]

        self.quantities_canonical_symbol = {"band_gaps": band_gaps,
                                            "refractive_indices": refractive_indices}

        self.sq_canonical_sym_as_dicts_no_value = copy.deepcopy(self.sq_custom_sym_as_dicts)
        self.sq_canonical_sym_as_dicts_no_value['band_gaps'] = self.sq_canonical_sym_as_dicts_no_value.pop('A')
        self.sq_canonical_sym_as_dicts_no_value['refractive_indices'] = self.sq_canonical_sym_as_dicts_no_value.pop('B')

        for d, sq in zip(self.sq_canonical_sym_as_dicts_no_value['band_gaps'], band_gaps):
            d.update({
                "internal_id": sq._internal_id,
                "symbol_type": "band_gap",
                "units": "electron_volt",
                "provenance": ProvenanceStore.from_provenance_element(sq.provenance)
            })
            d.pop('value')

        for d, sq in zip(self.sq_canonical_sym_as_dicts_no_value['refractive_indices'], refractive_indices):
            d.update({
                "internal_id": sq._internal_id,
                "symbol_type": "refractive_index",
                "units": "dimensionless",
                "provenance": ProvenanceStore.from_provenance_element(sq.provenance)
            })
            d.pop('value')

        self.sq_canonical_sym_values = {"band_gaps": [3.3, 2.1],
                                        "refractive_indices": [2.316340583741216, 2.593439239956374]}

        provenances_json['band_gaps'] = [
            {'@module': 'propnet.dbtools.storage',
             '@class': 'ProvenanceStore',
             'model': None,
             'inputs': None,
             'source': bg.provenance.source}
            for bg in band_gaps
        ]

        provenances_json['refractive_indices'] = [{
            '@module': 'propnet.dbtools.storage',
            '@class': 'ProvenanceStore',
            'model': 'band_gap_refractive_index_moss',
            'inputs': [{'@module': 'propnet.dbtools.storage',
                        '@class': 'ProvenanceStoreQuantity',
                        'data_type': 'NumQuantity',
                        'symbol_type': 'band_gap',
                        'internal_id': bg._internal_id,
                        'tags': [],
                        'provenance': pj}],
            'source': ri.provenance.source}
            for bg, pj, ri in zip(band_gaps,
                                  provenances_json['band_gaps'],
                                  refractive_indices)
        ]

        self.sq_canonical_sym_json_no_value = copy.deepcopy(self.sq_canonical_sym_as_dicts_no_value)

        for sym in ["band_gaps", "refractive_indices"]:
            for q, p in zip(self.sq_canonical_sym_json_no_value[sym], provenances_json[sym]):
                q['provenance'] = p

        self.quantity_with_uncertainty = NumQuantity.from_weighted_mean(b)
        self.sq_with_uncertainty_as_dict_no_numbers = {
            '@module': 'propnet.dbtools.storage',
            '@class': 'StorageQuantity',
            'internal_id': self.quantity_with_uncertainty._internal_id,
            'data_type': 'NumQuantity',
            'symbol_type': symbols['B'],
            'units': 'dimensionless',
            'provenance': ProvenanceStore.from_provenance_element(
                self.quantity_with_uncertainty.provenance),
            'tags': []}

        provenances_json = {
            '@module': 'propnet.dbtools.storage',
            '@class': 'ProvenanceStore',
            'model': 'aggregation',
            'inputs': [
                {'@module': 'propnet.dbtools.storage',
                 '@class': 'ProvenanceStoreQuantity',
                 'data_type': 'NumQuantity',
                 'symbol_type': self.custom_symbols_json['B'],
                 'internal_id': b['internal_id'],
                 'tags': [],
                 'provenance': b['provenance']}
                for b in self.sq_custom_sym_json['B']],
            'source': self.quantity_with_uncertainty.provenance.source
        }

        self.sq_with_uncertainty_json_no_numbers = copy.deepcopy(self.sq_with_uncertainty_as_dict_no_numbers)
        self.sq_with_uncertainty_json_no_numbers.update({"symbol_type": self.custom_symbols_json['B'],
                                                         "provenance": provenances_json})
        self.sq_with_uncertainty_numbers = {"value": 42.0,
                                            "uncertainty": 4.0}

        obj_symbol = symbols['C']
        self.object_quantity = QuantityFactory.create_quantity(obj_symbol, "Test string")
        self.sq_object_as_dict = copy.deepcopy(self.sq_custom_sym_as_dicts['A'][0])
        self.sq_object_as_dict.update({
            "data_type": "ObjQuantity",
            "symbol_type": symbols['C'],
            "internal_id": self.object_quantity._internal_id,
            "value": "Test string",
            "units": None,
            "provenance": ProvenanceStore.from_provenance_element(self.object_quantity.provenance)
        })
        self.sq_object_json = copy.deepcopy(self.sq_object_as_dict)
        self.sq_object_json.update(
            {"symbol_type": self.custom_syms_as_dicts['C'],
             "provenance": {'@module': 'propnet.dbtools.storage',
                            '@class': 'ProvenanceStore',
                            'model': None,
                            'inputs': None,
                            'source': self.object_quantity.provenance.source}}
        )

        # This setting allows dict differences to be shown in full
        self.maxDiff = None
示例#21
0
    def process_item(self, item):
        # Define quantities corresponding to materials doc fields
        # Attach quantities to materials
        item = MontyDecoder().process_decoded(item)
        logger.info("Populating material for %s", item['task_id'])
        material = Material()

        if 'created_at' in item.keys():
            date_created = item['created_at']
        else:
            date_created = ""

        provenance = ProvenanceElement(
            source={
                "source": self.source_name,
                "source_key": item['task_id'],
                "date_created": date_created
            })

        for mkey, property_name in self.materials_symbol_map.items():
            value = get(item, mkey)
            if value:
                material.add_quantity(
                    QuantityFactory.create_quantity(property_name,
                                                    value,
                                                    provenance=provenance))

        # Add custom things, e. g. computed entry
        computed_entry = get_entry(item)
        material.add_quantity(
            QuantityFactory.create_quantity("computed_entry",
                                            computed_entry,
                                            provenance=provenance))
        material.add_quantity(
            QuantityFactory.create_quantity("external_identifier_mp",
                                            item['task_id'],
                                            provenance=provenance))

        input_quantities = material.get_quantities()

        # Use graph to generate expanded quantity pool
        logger.info("Evaluating graph for %s", item['task_id'])
        graph = Graph()
        graph.remove_models({
            "dimensionality_cheon":
            DEFAULT_MODEL_DICT['dimensionality_cheon'],
            "dimensionality_gorai":
            DEFAULT_MODEL_DICT['dimensionality_gorai']
        })
        new_material = graph.evaluate(material)

        # Format document and return
        logger.info("Creating doc for %s", item['task_id'])
        # Gives the initial inputs that were used to derive properties of a
        # certain material.

        doc = {
            "inputs":
            [StorageQuantity.from_quantity(q) for q in input_quantities]
        }
        for symbol, quantity in new_material.get_aggregated_quantities().items(
        ):
            all_qs = new_material._symbol_to_quantity[symbol]
            # Only add new quantities
            # TODO: Condition insufficiently general.
            #       Can end up with initial quantities added as "new quantities"
            if len(all_qs) == 1 and list(all_qs)[0] in input_quantities:
                continue
            # Write out all quantities as dicts including the
            # internal ID for provenance tracing
            qs = [StorageQuantity.from_quantity(q).as_dict() for q in all_qs]
            # THE listing of all Quantities of a given symbol.
            sub_doc = {
                "quantities": qs,
                "mean": unumpy.nominal_values(quantity.value).tolist(),
                "std_dev": unumpy.std_devs(quantity.value).tolist(),
                "units":
                quantity.units.format_babel() if quantity.units else None,
                "title": quantity._symbol_type.display_names[0]
            }
            # Symbol Name -> Sub_Document, listing all Quantities of that type.
            doc[symbol.name] = sub_doc

        doc.update({
            "task_id": item["task_id"],
            "pretty_formula": item["pretty_formula"]
        })
        return jsanitize(doc, strict=True)
示例#22
0
    def super_evaluate(self, material, allow_model_failure=True):
        """
        Given a SuperMaterial object as input, creates a new SuperMaterial
        object to include all derivable properties.  Returns a reference to
        the new, augmented SuperMaterial object.

        Args:
            material (SuperMaterial): material for which properties
                will be expanded.

        Returns:
            (Material) reference to the newly derived material object.
        """

        if not isinstance(material, CompositeMaterial):
            raise Exception("material provided is not a SuperMaterial: " +
                            str(type(material)))

        # Evaluate material's sub-materials
        evaluated_materials = list()
        for m in material.materials:
            logger.debug("Evaluating sub-material: " + str(id(m)))
            if isinstance(m, CompositeMaterial):
                evaluated_materials.append(self.super_evaluate(m))
            else:
                evaluated_materials.append(self.evaluate(m))

        # Run all SuperModels in the graph on this SuperMaterial if
        # a material mapping can be established.  Store any derived quantities.
        all_quantities = defaultdict(set)
        for (k, v) in material._symbol_to_quantity:
            all_quantities[k].add(v)

        to_return = CompositeMaterial(evaluated_materials)
        to_return._symbol_to_quantity = all_quantities

        logger.debug("Evaluating SuperMaterial")

        for model in self._composite_models.values():

            logger.debug("\tEvaluating Model: " + model.name)

            # Establish material mappings for the given input set.

            mat_mappings = model.gen_material_mappings(to_return.materials)

            # Avoid ambiguous or impossible mappings, at least for now.
            if len(mat_mappings) != 1:
                continue

            mat_mapping = mat_mappings[0]

            # Go through input sets

            for property_input_sets in model.evaluation_list:

                logger.debug("\t\tGenerating input sets for: " +
                             str(property_input_sets))

                # Create a quantity pool from the appropriate materials.
                # Modify inputs for use in generate_input_sets

                temp_pool = defaultdict(set)
                combined_list = []
                mat_list = []
                symbol_list = []
                for item in property_input_sets:
                    combined_list.append(item)
                    mat_list.append(CompositeModel.get_material(item))
                    symbol_list.append(CompositeModel.get_symbol(item))
                for i in range(0, len(mat_list)):
                    if mat_list[
                            i] == None:  # Draw symbol from the CompositeMaterial
                        mat = to_return
                    else:
                        mat = mat_mapping[mat_list[i]]
                    for q in mat._symbol_to_quantity[symbol_list[i]]:
                        temp_pool[combined_list[i]].add(q)
                input_sets = self.generate_input_sets(combined_list, temp_pool)

                for input_set in input_sets:

                    logger.debug("\t\t\tEvaluating input set: " +
                                 str(input_set))

                    # Check if input_set can be evaluated -- input_set must pass the necessary model constraints
                    if not model.check_constraints(input_set):
                        logger.debug(
                            "\t\t\tInput set failed -- did not pass model constraints."
                        )
                        continue

                    # Try to evaluate input_set:
                    evaluate_set = dict(zip(combined_list, input_set))
                    output = model.evaluate(evaluate_set,
                                            allow_failure=allow_model_failure)
                    success = output.pop('successful')
                    if not success:
                        logger.debug(
                            "\t\t\tInput set failed -- did not produce a successful output."
                        )
                        continue

                    # input_set led to output from the Model -- add output to the SuperMaterial

                    logger.debug("\t\t\tInput set produced successful output.")
                    for symbol, quantity in output.items():
                        st = self._symbol_types.get(symbol)
                        if not st:
                            raise ValueError(
                                "Symbol type {} not found".format(symbol))
                        q = QuantityFactory.create_quantity(st, quantity)
                        to_return._symbol_to_quantity[st].add(q)
                        logger.debug("\t\t\tNew output: " + str(q))

        # Evaluate the SuperMaterial's quantities and return the result.
        mappings = self.evaluate(to_return)._symbol_to_quantity
        to_return._symbol_to_quantity = mappings
        return to_return
示例#23
0
    def evaluate(self, symbol_quantity_dict_in, allow_failure=True):
        """
        Given a set of property_values, performs error checking to see
        if the corresponding input symbol_values represents a valid
        input set based on the self.connections() method. If so, returns
        a dictionary representing the value of plug_in applied to the
        input_symbols. The dictionary contains a "successful" key
        representing if plug_in was successful.

        The key distinction between evaluate and plug_in is properties
        in properties out vs. symbols in symbols out.  In addition,
        evaluate also handles any requisite unit_mapping

        Args:
            symbol_quantity_dict ({property_name: Quantity}): a mapping of
                symbol names to quantities to be substituted
            allow_failure (bool): whether or not to catch
                errors in model evaluation

        Returns:
            dictionary of output properties with associated values
            generated from the input, along with "successful" if the
            substitution succeeds
        """
        # Remap symbols and units if symbol map isn't none

        symbol_quantity_dict = self.map_properties_to_symbols(
            symbol_quantity_dict_in)

        input_symbol_quantity_dict = {k: v for k, v in symbol_quantity_dict.items()
                                      if not (k in self.constraint_symbols
                                              and k not in self.all_input_symbols)}

        for (k, v) in symbol_quantity_dict.items():
            # replacing = self.symbol_property_map.get(k, k)
            replacing = self.symbol_property_map.get(k)
            # to_quantity() returns original object if it's already a BaseQuantity
            # unlike Quantity() which will return a deep copy
            symbol_quantity_dict[k] = QuantityFactory.to_quantity(replacing, v)

        # TODO: Is it really necessary to strip these?
        # TODO: maybe this only applies to pymodels or things with objects?
        # strip units from input and keep for reassignment
        symbol_value_dict = {}

        for symbol, quantity in symbol_quantity_dict.items():
            # If unit map convert and then scrub units
            if self.unit_map.get(symbol):
                quantity = quantity.to(self.unit_map[symbol])
                symbol_value_dict[symbol] = quantity.magnitude
            # Otherwise use values
            else:
                symbol_value_dict[symbol] = quantity.value

        contains_complex_input = any(NumQuantity.is_complex_type(v) for v in symbol_value_dict.values())
        input_symbol_value_dict = {k: symbol_value_dict[k] for k in input_symbol_quantity_dict.keys()}

        # Plug in and check constraints
        try:
            with PrintToLogger():
                out = self.plug_in(input_symbol_value_dict)
        except Exception as err:
            if allow_failure:
                return {"successful": False,
                        "message": "{} evaluation failed: {}".format(self, err)}
            else:
                raise err
        if not self.check_constraints({**symbol_value_dict, **out}):
            return {"successful": False,
                    "message": "Constraints not satisfied"}

        provenance = ProvenanceElement(
            model=self.name, inputs=list(input_symbol_quantity_dict.values()),
            source="propnet")

        out = self.map_symbols_to_properties(out)
        unit_map_as_properties = self.map_symbols_to_properties(self.unit_map)
        for symbol, value in out.items():
            try:
                quantity = QuantityFactory.create_quantity(
                    symbol, value, unit_map_as_properties.get(symbol),
                    provenance=provenance)
            except SymbolConstraintError as err:
                if allow_failure:
                    errmsg = "{} symbol constraint failed: {}".format(self, err)
                    return {"successful": False,
                            "message": errmsg}
                else:
                    raise err

            if quantity.contains_nan_value():
                return {"successful": False,
                        "message": "Evaluation returned invalid values (NaN)"}
            # TODO: Update when we figure out how we're going to handle complex quantities
            # Model evaluation will fail if complex values are returned when no complex input was given
            # Can surely handle this more gracefully, or assume that the users will apply constraints
            if quantity.contains_imaginary_value() and not contains_complex_input:
                return {"successful": False,
                        "message": "Evaluation returned invalid values (complex)"}

            out[symbol] = quantity

        out['successful'] = True
        return out