<jupyter_start><jupyter_text>Tutorial on using Propnet The following is a tutorial designed to give a base overview of the classes and constructs used in the Propnet project. For each class an example of its construction and base usage is provided.  Defining a Property Network: Propnet A Graph object tells us about all the property types and models currently available for use.The mappings contained in this object define an interconnected network of materials properties. In this form, the Graph object can be used to enumerate and analyze links between differnet materials properties.<jupyter_code>from propnet.core.graph import Graph
g = Graph()<jupyter_output><empty_output><jupyter_text>You can print Propnet to see the property types and models it supports.<jupyter_code>print(g)<jupyter_output>Propnet Printout

Properties
	formula
	dimensionality
	structure_oxi
	oxide_type
	is_metallic
	magnetic_order
	lattice
	external_identifier_mp
	computed_entry
	external_identifier_aflow
	structure
	potentially_ferroelectric
	prototype
	composition
	unit_cell_heat_capacity_constant_volume
	atomic_density
	elastic_tensor_voigt
	gruneisen_parameter
	interplanar_spacing
	sound_velocity_longitudinal
	goldschmidt_tolerance_factor
	phonon_mean_free_path
	thermal_conductivity
	refractive_index
	piezoelectric_modulus_longitudinal_max_direction
	electron_concentration
	ionic_radius_b
	absorption_coefficient
	p_wave_modulus
	cost_per_kg
	extinction_coefficient
	total_magnetization_per_volume
	electronic_thermal_conductivity
	pilling_bedworth_ratio
	transmittance
	interatomic_spacing
	band_gap_pbe
	electrical_resistivity
	relative_permittivity
	snyder_acoustic_sound_velocity
	cost_per_mol
	sound_velocity_mean
	volume_unit_cell
	youngs_modulus
	dielectric_figure_of_merit_energy
	mass_per_atom
	pois[...]<jupyter_text>Defining a Material Property A Symbol object is used to represent types of properties (such as Young's Modulus) or conditions (such as Temperature).- All Symbol objects are accessible in a global Registry entry.- Various metadata for each SymbolType can be accessed as shown below.<jupyter_code># Import built-in symbols
import propnet.symbols
# Access them through symbol registry
from propnet.core.registry import Registry
symbol_object = Registry("symbols")['youngs_modulus']
print(symbol_object)<jupyter_output>Symbol: youngs_modulus<jupyter_text>A Quantity object is used to represent values of properties (such as Young's Modulus = 200GPa) or conditions (such as temperature = 300K).- All Quantity objects have a Symbol giving the type of property represented by the value.- All Quantity objects must be created at runtime by specifying a value during instantiation.- All Quantity objects have a list of strings called "tags" used to further label the property.Currently, two Quantity object types exist to support different kinds of data:- NumQuantity supports numerical quantities, both scalar, vector, and matrix.- ObjQuantity supports any JSON-serializable Python object (using MSONable). Useful for structure or formula.Both types can be created using a QuantityFactory.<jupyter_code>from propnet.core.quantity import QuantityFactory
steel_youngs_modulus = QuantityFactory.create_quantity('youngs_modulus', 200, 'GPa', tags=['mild steel'])
print(steel_youngs_modulus)
print(type(steel_youngs_modulus))

table_salt_formula = QuantityFactory.create_quantity('formula', 'NaCl', tags=['table salt'])
print(table_salt_formula)
print(type(table_salt_formula))<jupyter_output><youngs_modulus, 200 gigapascal, ['mild steel']>
<class 'propnet.core.quantity.NumQuantity'>
<formula, NaCl, ['table salt']>
<class 'propnet.core.quantity.ObjQuantity'><jupyter_text>Defining a Material A Material object is used to represent a collection of information known about a given material.A Material object can be created empty and then populated with property data, or created from a list of properties.<jupyter_code>from propnet.core.materials import Material
from propnet.core.quantity import NumQuantity, ObjQuantity
mild_steel = Material()
youngs_modulus = NumQuantity('youngs_modulus', 200, 'gigapascal')
mild_steel.add_quantity(youngs_modulus)
print(mild_steel)

table_salt_formula = ObjQuantity('formula', 'NaCl')
table_salt = Material([table_salt_formula])
print(table_salt)<jupyter_output>Material: 0x127005898

	youngs_modulus
		<youngs_modulus, 200 gigapascal, []>

Material: 0x127005a58

	formula
		<formula, NaCl, []><jupyter_text>Combining Models, Materials, and Symbols As illustrated, a Graph object contains information for connecting many different models and symbol types. This forms an abstract web of interconnected variables without any values specified.On the other hand, a Material object represents a grouping of values for different variables. These are represented as a collection of Quantity objects identified with the material.At runtime, a single Graph object can be used to "evaluate" a Material object. This procedure allows values to be plugged in to variables. Assuming the required inputs for a model all have values, the Graph object can then dynamically predict the values for the output variables of the model.In this example, we start with a refractive index and a permittivity. As a result many other properties are derived and printed below.<jupyter_code>from propnet.core.materials import Material
from propnet.core.graph import Graph
from propnet.core.quantity import QuantityFactory
# Import built-in propnet models
import propnet.models

g = Graph()

silica = Material()
refractive_index = QuantityFactory.create_quantity('refractive_index', 1.458)
relative_permittivity = QuantityFactory.create_quantity('relative_permittivity', 3.9)

silica.add_quantity(refractive_index)
silica.add_quantity(relative_permittivity)

silica = g.evaluate(silica)
print(silica)<jupyter_output>WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
Material: 0x126af8cc0

	refractive_index
		<refractive_index, 1.458 dimensionless, []>

	relative_permittivity
		<relative_permittivity, 3.9 dimensionless, []>

	band_gap
		<band_gap, 21.02294283374089 electron_volt, []>
		<band_gap, 34.44429680416945 electron_volt, []>
		<band_gap, 8.44705399783503 electron_volt, []>

	relative_permeability
		...

	dielectric_figure_of_merit_current_leakage
		...
		...
		...

	band_gap_gw
		<band_gap_gw, 21.05104492358802 electron_volt, []>
		<band_gap_gw, 34.49929539495929 electron_volt, []>
		<band_gap_gw, 8.449953905646305 electron_volt, []>

	band_gap_pbe
		...
		...
		...
		...
		...
		...

	dielectric_figure_of_merit_energy
		...
		...
		...

	is_metallic
		<is_metallic, False, []>
		<is_metallic, False, []>
		<is_metallic, False, []><jupyter_text>Working with Models A Model object is used to represent a relationship between different materials property variables. This object can be directly manipulated and stores relavent metadata available as direct attributes.- All Models are imported as classes at runtime.- A Model class must be instantiated to be used at runtime.<jupyter_code>import propnet.models
from propnet.core.graph import Graph
g = Graph()
# This information can be obtained from Registry("models") as well
model = g.get_models()['refractive_indexfrom_rel_perm']
print(model.description)
print(model.name)
print()
print(model.equations)<jupyter_output>The refractive index gives the factor by which the speed of light is reduced in
a medium.  Likewise, modeling the induced magnetic and electric dipoles as linear
within a material, a relative spatial electrical permittivity and relative spatial
magnetic permeability arise from consideration of the total electrical and magnetic
fields.

From the Maxwell Relations, the index of refraction is equal to the geometric mean
of the relative permittivity and the relative permeability.

refractive_indexfrom_rel_perm

['n = sqrt(Ur*Er)']<jupyter_text>Model Class Structure - Basic Overview The Model class is a generally-defined interface, and subclasses may alter many aspects of its underlying functionality.Most Model objects will contain equations, symbols, and connections attributes. These define the core functionality of the model: The equations attribute will contain a list of sympy-parsable expressions. These expressions imply trivial equations such that the expression is equal to zero. (ie. x - 4 for corresponding equation x = 4)<jupyter_code>print(model.equations)<jupyter_output>['n = sqrt(Ur*Er)']<jupyter_text>The symbol_mapping attribute maps the symbols (ie. n) used in the equations to Symbol objects (ie. refractive index) used in the Property Network.<jupyter_code>print(model.variable_symbol_map)<jupyter_output>{'Ur': 'relative_permittivity', 'n': 'refractive_index', 'Er': 'relative_permeability'}<jupyter_text>The connections attribute shows what outputs can be generated from a set of inputs, following a standard format. It also contains metadata for evaluating the models using equations.<jupyter_code>print(model.connections)<jupyter_output>[{'inputs': ['n', 'Er'], 'outputs': ['Ur'], '_sympy_exprs': {'Ur': [n**2/Er]}, '_lambdas': {'Ur': <function _lambdifygenerated at 0x1276bdae8>}}, {'inputs': ['Ur', 'Er'], 'outputs': ['n'], '_sympy_exprs': {'n': [sqrt(Er*Ur)]}, '_lambdas': {'n': <function _lambdifygenerated at 0x1276bd268>}}, {'inputs': ['Ur', 'n'], 'outputs': ['Er'], '_sympy_exprs': {'Er': [n**2/Ur]}, '_lambdas': {'Er': <function _lambdifygenerated at 0x1276bd7b8>}}]<jupyter_text>A Model can be evaluated to generate outputs if given a complete set of inputs.Given the relative permeability and permittivity, the Refractive Index From Relative Permeability model can correctly calculate the index of refraction.This is given by 'n' in the dictionary below.<jupyter_code>model.plug_in({'Ur': 0.54, 'Er': 3.9})<jupyter_output><empty_output><jupyter_text>Loading Materials Data Material properties can be loaded in from the Materials Project so they don't need to be defined and added manually.Accessing Materials Project data requires an API key. You must input your own API key below to run the sample. You can locate your api key by logging into materialsproject.org and visiting the dashboard.<jupyter_code>from propnet.ext.matproj import MPRester
# If you have a MP API key, you can enter it below.
my_api_key = None
mpr = MPRester(my_api_key)
silica = mpr.get_quantities_for_mpid('mp-546794')
print(silica)<jupyter_output>{'material_id': 'mp-546794', 'band_gap.search_gap.band_gap': 5.703399999999999, 'computed_entry': ComputedEntry mp-546794 - Si16 O32
Energy = -379.9357
Correction = -22.4733
Parameters:
run_type = GGA
is_hubbard = False
pseudo_potential = {'functional': 'PBE', 'labels': ['Si', 'O'], 'pot_type': 'paw'}
hubbards = {}
potcar_symbols = ['PBE Si', 'PBE O']
oxide_type = oxide
Data:
oxide_type = oxide, 'diel.n': 1.4782042033043565, 'diel.poly_total': 3.8824606666666672, 'diel.e_total': [[3.827947000000001, 0.0, 0.0], [0.0, 3.827947000000001, 1.9736472213591742e-17], [0.0, 1.9736472213591742e-17, 3.9914880000000004]], 'diel.poly_electronic': 2.1850876666666674, 'pretty_formula': 'SiO2', 'e_above_hull': 0, 'elasticity.elastic_tensor_original': [[91.20978053601698, -37.72413000560153, 19.319911320055816, 0.0, 0.0, 0.0], [-37.92987903108265, 91.35007557673609, 19.271159227260735, 0.0, 0.0, 0.0], [15.125369111160174, 15.125369111160174, 85.61256491670275, 0.0, 0.0, 0.0], [0.1477369604481241, 0.587[...]<jupyter_text>Working with Units  Symbol ObjectsIt is best practice to specify units when defining a Quantity object or plugging values into Models. However, in the case that no units are supplied, default units are inferred based on those provided in the corresponding Symbol object.For instance, youngs_modulus is a Symbol with the default units of gigapascal. In the example above:           youngs_modulus = Symbol('youngs_modulus', 200, [])           youngs_modulus automatically has the value 200 gigapascals despite no units being provided.It is possible to provide your own units by working with the unit registry (ureg).The examples below demonstrate this capability using the method:                         ureg.Quantity(value, unit)            propnet will automatically convert the units you supply into canonical units behind the scenes for use in models that use the property.<jupyter_code>from propnet import ureg
from propnet.core.materials import Material
from propnet.core.quantity import QuantityFactory

mild_steel = Material()
yield_strength = QuantityFactory.create_quantity('yield_stress', ureg.Quantity(53700, 'psi'), [])
mild_steel.add_quantity(yield_strength)
print(mild_steel)<jupyter_output>WARNING: No units supplied, assuming default units from symbol.
Material: 0x126d60320

	yield_stress
		<yield_stress, 0.3702484666431411 gigapascal, []><jupyter_text>ModelsModels can be evaluated using two methods: `plug_in` or `evaluate`.The difference: `evaluate` is symbols in, symbols out; `plug_in` is variables in, variables out.`evaluate` requires a dictionary keyed by symbol name (e.g. "youngs_modulus") and values which are propnet Quantity objects. Units are converted to canonical units on the fly.`plug_in` requires a dictionary keyed by variable name (e.g. "Y") and values which are Python floats. No unit conversions are done.In the example below we use propnet to calculate the Peierls-Nabarro Stress for a disloaction on the (111) plane in Aluminum (FCC) using custom units of psi for shear modulus.<jupyter_code>from propnet import ureg
import propnet.models
from propnet.core.registry import Registry
from propnet.core.quantity import QuantityFactory

model = Registry("models")['peierls_stress']

result = model.evaluate({
    'shear_modulus': QuantityFactory.create_quantity("shear_modulus", 3.9e6, 'psi'),
    'interplanar_spacing': QuantityFactory.create_quantity("interplanar_spacing", 4.046 / 3**(1/2)),
    'interatomic_spacing': QuantityFactory.create_quantity("interatomic_spacing", 4.046 / 2**(1/2)),
    'poisson_ratio': QuantityFactory.create_quantity("poisson_ratio", 0.33)
})
print(result)

# Must use gigapascal, because model assumes gigapascal. See section below for conversions
G = 26.8896
a = 4.046 / 3**(1/2)  # Angstroms automatically assumed by the model.
b = 4.046 / 2**(1/2)  # Angstroms automatically assumed by the model.
nu = 0.33

tau = model.plug_in({'G': G, 'a': a, 'b': b, 'nu': nu})
print(tau)<jupyter_output>WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
{'peierls_stress': <peierls_stress, 0.012711228013154446 gigapascal, []>, 'successful': True}
{'T_pn': 0.01271125002140798}<jupyter_text>Converting UnitsGiven you have an output with units, it is straightforward to convert between units using Pint (the backend package used by propnet for units).The `to` operation can be performed on propnet NumQuantity objects or pint ureg.Quantity objects.<jupyter_code># Converting to megapascals.
print(result['peierls_stress'].to('megapascal'))<jupyter_output><peierls_stress, 12.711228013154447 megapascal, []><jupyter_text>Creating Custom Models and Properties The property network comes with many different models and properties pre-loaded and ready for use.To add additional properties to the project can be accomplished in two different forms:- Defining additional .yaml files (symbols/models) or .py files (models) in the propnet.models or propnet.symbols folders. For these changes to take effect, propnet must be re-loaded.- Defining additional Model (EquationModel, PyModel, etc.) and Symbol classes at runtime.Both approaches will be detailed below.  Defining new model files:Depending on the nature of the model, it may be prudent choose a serialized yaml file or a Python module to define the new model. YAML files are used for simple models which can be represented by a single equation and generally only deal with scalar quantities. Python module models are for more complex models which involve matrices/vectors or more complex evaluation.The syntax for an equation-based model stored in a yaml file is as follows:      Field Name    Required?    Field Format     Field Description        name    yes    string     name of the model, must match the file name, no spaces        categories    yes, but may be empty    list of strings     any user-specific tags applying to the model        description    yes, but may be empty    string     ideally, a verbose description of the model, its assumptions, etc.        implemented_by    yes    list of strings     GitHub usernames of author(s) of the model. May not be empty.        references    no, but recommended    list of strings     url, doi, or isbn for citing the model, use "type:data" to specify, e.g. "url:https://www...."        equations    yes    list of strings     list of equations that create the model        variable_symbol_map    no, but see description    dictionary, string keys, string values     mapping from variables used in the equation to symbol names used in propnet, e.g. {"m": "mass"}. If not specified, the variable names will be assumed to be the same as the symbol names.        units_for_evaluation    no, but see description    dictionary, string keys, string values     mapping from variables used in the equation to units required for proper evaluation. It is required if the model is empirical or includes constants that have dimensions.        connections    no, but see description    list of dictionaries with keys: 'inputs', 'outputs' mapping to lists of symbol strings used in the model     contains valid input/output symbol combinations. If omitted, this will be derived automatically from the equation. For example,    F=m*a will produce {"inputs": ["mass", "acceleration"], "outputs": ["force"]} and no other combination.        solve_for_all_symbols    no, default false    boolean     If true, propnet will attempt to derive all possible input/output combinations from the equation. Default is false.        constraints    no    list of strings    (in)equality statements for input or output variables that must be true in order for the model to be evaluated successfully. e.g. "Eg > 0"        test_data    no, but recommended    list of dictionaries with keys: 'inputs', 'outputs', which have dictionaries, keyed by variables, valued by numerical or string input(s) and expected numerical or string output(s)    data used to test the model. Data may be specified as raw numbers, assumed to be the unit specified by units_for_evaluation or by the default symbol unit. Data may also be specified as strings of numbers and units. Example: [{'inputs': {'m': '3 kg', 'a': '2 m/s**2'}, 'outputs': {'F': '6 N'}}]  Python models are constructed in a very similar manner to the YAML file. To create a new Python model:1. Create a new py module in the models/python folder with the name of your model as the file name.2. In your module, define a function which takes a dictionary of inputs, keyed by input variable name, and returns a dictionary, keyed by output variable name. This is your "plug-in" function.3. Define a module-level variable `config` which holds a dictionary with the fields specified in the YAML construction above. The only exceptions are that `connections` must be explicitly included, `solve_for_all_symbols` and `equations` cannot be used, and `config` must have a field `plug_in` which is valued by a reference to your plug-in function from step 2.4. If your model requires Python packages not already required by propnet, be sure to add them to "requirements.txt" under the " specific models" section.  Defining new Symbol files:Symbols require yaml file to be defined in the propnet/symbols folder. For numerical properties which aren't environmental conditions, place them in the "properties" folder. For properties which do not hold numbers (e.g. formula), place them in the "objects" folder.The syntax for a yaml definition is as follows:      Field Name    Required?    Field Format     Field Description        name    yes    string     name of the property, must match the file name        category    yes    must be one of ('property', 'condition', 'object')     specifies the type of data the symbol represents        units    depends, see decription    string     the units used to represent this symbol, must be specified for property and condition symbols, must be excluded for object symbols.        display_names    no, but recommended    list of strings     descriptive name(s) that represent this symbol        display_symbols    no, but recommended    list of strings     any short string symbols useful for representing this symbol, for example, how it would be represented in an equation        shape    depends, see description    number or list of numbers    expected dimesions of a numerical symbol. 1 or [1]=scalar, n or [n]=vector of length n, [m, n]=matrix of m-by-n size. required for properties and conditions, must be excluded for objects.        constraint    no    string     string representing a constraint equation required for the symbol, e.g. 'bulk_modulus > 0'. Only applicable to property and condition symbols.        object_type    no    string     string representing the Python object type expected by an object symbol. e.g. "str" or "pymatgen.core.lattice.Lattice". Only applicable to object symbols. If specified, quantities created with this symbol will be cast as this type.        comment    no    string     any comments pertinent to the symbol        default_value    no    float     default value for a quantity made from this symbol, generally used for conditions (may be deprecated)    A Grand Example<jupyter_code>silicon = Material()
silicon.add_quantity(QuantityFactory.create_quantity('temperature', 300, []))
silicon.add_quantity(QuantityFactory.create_quantity('band_gap', 1.12, []))
silicon.add_quantity(QuantityFactory.create_quantity('electrical_conductivity', 4.34E-4, []))

aluminum = Material()
aluminum.add_quantity(QuantityFactory.create_quantity('temperature', 300, []))
aluminum.add_quantity(QuantityFactory.create_quantity('band_gap', 0, []))
aluminum.add_quantity(QuantityFactory.create_quantity('electrical_conductivity', 3.69E7, []))

g = Graph()
silicon = g.evaluate(silicon)
aluminum = g.evaluate(aluminum)

print("Silicon's Graph")
print(silicon)
print()
print("Aluminum's Graph")
print(aluminum)<jupyter_output>WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
WARNING: No units supplied, assuming default units from symbol.
Silicon's Graph
Material: 0x127e4ad68

	temperature
		<temperature, 300 kelvin, []>

	band_gap
		<band_gap, 1.12 electron_volt, []>

	electrical_conductivity
		...

	refractive_index
		<refractive_index, 3.194112 dimensionless, []>
		...
		...
		...
		...

	band_gap_gw
		...

	band_gap_pbe
		...
		...

	is_metallic
		<is_metallic, False, []>

	electronic_thermal_conductivity
		...

	electrical_resistivity
		...


Aluminum's Graph
Material: 0x127005a90

	temperature
		<temperature, 300 kelvin, []>

	band_gap
		<band_gap, 0 electron_volt, []>

	electrical_conductivity
		...

	refractive_index
		<refractive_inde[...]
{'fraction_non_alphanumeric': 0.07617952648611821, 'fraction_numerical': 0.032660534832226196, 'mean_word_length': 5.062209602478059, 'pattern_counts': {'":': 3, '<': 81, '<?xml version=': 0, '>': 82, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 6, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '89913', 'license': 'BSD-3-Clause-LBNL', 'n_tokens_mistral': 7376, 'n_tokens_neox': 6495, 'n_words': 2612, 'path': 'demo/Getting Started.ipynb', 'repo_name': 'nile0316/propnet', 'size': '23484'}