from typing import Tuple
import numpy as np
from TidalPy.exceptions import BadValueError, ParameterMissingError, ParameterValueError
from TidalPy.utilities.types import float_eps
from TidalPy.Extending.burnman.package import burnman, burnman_installed
from TidalPy.Extending.burnman.defaults import default_burnman_layer_params
from TidalPy.Extending.burnman.material.helper import find_material
from TidalPy.logger import get_logger
log = get_logger("TidalPy")
[docs]
def build_layer(layer_name: str, layer_config: dict, burnman_verbose: str = False) -> burnman.Layer:
""" Build a Burnman layer
Parameters
----------
layer_name : str
Name of the layer
layer_config : dict
Dictionary of layer configurations used to initialize the layer
burnman_verbose : bool, default = False
Determines the verboseness of the BurnMan package.
Returns
-------
layer : burnman.Layer
An initialized burnman layer
"""
# Record information
log.debug(f'Burnman layer {layer_name} being built.')
if not burnman_installed:
log.debug("Burnman install not found.")
raise ImportError('Burnman package not found.')
# Load in defaults
layer_config = {**default_burnman_layer_params, **layer_config}
# Check for missing parameters
for param_name in ['radii', 'material', 'material_source', 'temperature_mode']:
if param_name not in layer_config:
raise ParameterMissingError(f'BurnMan Layer requires parameter: {param_name}')
material = layer_config['material']
material_source = layer_config['material_source']
# Determine if the material is a composite or not
composite = False
if type(layer_config['material']) in [list, tuple]:
log.debug(f'Composite material encountered for layer: {layer_name}.')
composite = True
if composite:
try:
material_fractions = layer_config['material_fractions']
except KeyError:
raise ParameterMissingError("Multiple materials provided but no material_fraction's were given.")
if type(material_fractions) not in [list, tuple]:
raise ParameterValueError('Material fractions must be provided as a list or tuple.')
if type(material_source) not in [list, tuple]:
raise ParameterValueError('Multiple materials provided with only one source.')
if len(material) != len(material_source):
raise ParameterValueError('Number of composite materials is not the same as number of material sources.')
if len(material) != len(material_fractions):
raise ParameterValueError('Number of composite materials is not the same as number of material fractions.')
if type(material_fractions) is tuple:
# Burnman only supports lists for this
material_fractions = list(material_fractions)
material_instances = list()
for mat_name, mat_source in zip(material, material_source):
# Find BurnMan material based on material and source name
material_class = find_material(mat_name, mat_source)
material_instances.append(material_class())
# Build composite material
init_material = burnman.Composite(material_instances, material_fractions)
else:
# Find BurnMan material based on material and source name
material_class = find_material(layer_config['material'], layer_config['material_source'])
# Initialize material
init_material = material_class()
# Make layer and load in material
radii = layer_config['radii']
layer = burnman.Layer(name=layer_name, radii=radii, verbose=burnman_verbose)
layer.set_material(init_material)
# Don't keep radii in the config, otherwise it will save to the json file as a large list
del layer_config['radii']
# Load in temperature model
if layer_config['temperature_mode'] == 'adiabatic':
if layer_config.get('temperature_top', None) is None:
layer.set_temperature_mode('adiabatic')
else:
layer.set_temperature_mode('adiabatic', temperature_top=layer_config['temperature_top'])
elif layer_config['temperature_mode'] == 'user-defined':
layer.set_temperature_mode('user-defined', layer_config['temperature_fixed'] * np.ones_like(radii))
else:
layer.set_temperature_mode(layer_config['temperature_mode'])
return layer
[docs]
def build_burnman_world(planet_config: dict, verbose: bool = False) -> Tuple[burnman.Planet, Tuple[burnman.Layer, ...]]:
""" Builds a BurnMan planet from a provided configuration dictionary.
Parameters
----------
planet_config : dict
Dictionary of planet configurations used to initialize layers and the planet.
verbose : bool = False
If True, BurnMan will print more information to console.
Returns
-------
burnman_world : burnman.Planet
An initialized and built burnman planet class
burnman_layers : Tuple[burnman.Layer, ...]
An ordered list of initialized burnman layers
"""
log.debug(f"Building BurnMan world: {planet_config['name']}")
if not burnman_installed:
log.debug("Burnman install not found.")
raise ImportError('Burnman package not found.')
# Store Layer information
try:
layers = planet_config['layers']
except KeyError:
raise ParameterMissingError('BurnMan Planet must have at least one layer.')
# Set defaults if none are provided
last_layer_radius = 0.
for layer_i, (layer_name, layer_config_user) in enumerate(layers.items()):
log.debug(f"Initializing layer: {layer_name}")
# Check if layer has must-have parameters
for param_name in ['type', 'material', 'radius']:
if param_name not in layer_config_user:
raise ParameterMissingError(f'BurnMan Layer requires parameter: {param_name}')
# Load in defaults for non-required parameters if not present
layer_config = {**default_burnman_layer_params, **layer_config_user}
# Load in calculated parameters
if 'thickness' not in layer_config:
if layer_i == 0:
layer_config['thickness'] = layer_config['radius']
else:
layer_config['thickness'] = layer_config['radius'] - last_layer_radius
layer_config['radius_upper'] = layer_config['radius']
layer_config['radius_lower'] = layer_config['radius_upper'] - layer_config['thickness']
layer_config['radii'] = np.linspace(
layer_config['radius_lower'], layer_config['radius_upper'],
layer_config['slices'], endpoint=True
)
# Check for physical consistency
if layer_config['thickness'] <= float_eps:
raise BadValueError('Layer thickness must be non-zero')
if layer_i > 0:
if layer_config['radius_lower'] != last_layer_radius:
raise BadValueError('Layers must stack on top of one another')
last_layer_radius = layer_config['radius']
# Put the new config back into the layers storage
layers[layer_name] = layer_config
# Build BurnMan layers
log.debug("Building BurnMan Layers")
burnman_layers_byname = {layer_name: build_layer(layer_name, layer_data) for layer_name, layer_data in
layers.items()}
burnman_layers = [burnman_layers_byname[layer_name] for layer_name in layers]
# Build BurnMan Planet
log.debug(f"Initializing BurnMan Planet for {planet_config['name']}")
burnman_world = burnman.Planet(planet_config['name'], burnman_layers, verbose=verbose)
# Note: This section can be slow!
burnman_world.make()
log.debug('BurnMan world construction completed')
# Change layer list to tuple to emphasize that these should not change or mutate
burnman_layers = tuple(burnman_layers)
return burnman_world, burnman_layers