Source code for AFL.automation.prepare.Component
import numpy as np
import periodictable
import copy
import numbers
from pyparsing import ParseException
from AFL.automation.shared.units import units, AVOGADROS_NUMBER, enforce_units
from AFL.automation.prepare.PrepType import PrepType
[docs]
class Component(object):
'''Base class for all materials
This class defines all of the basic properties and methods to be shared across material objects
'''
[docs]
def __init__(self,name,description):
self.name = name
self.description = description
self._mass = 1.0*units('mg')
self._density = None
self._formula = None
self._sld = None
self.preptype = PrepType.BaseComponent
[docs]
def emit(self):
return {
'name':self.name,
'description':self.description,
'density':self.density,
'formula':self.formula,
'sld':self.sld,
'preptype':self.preptype
}
def __str__(self):
out_str = '<Component '
out_str += f' M={self.mass:4.3f}' if self._has_mass else ' M=None'
out_str += f' V={self.volume:4.3f}' if self._has_volume else ' V=None'
out_str += f' D={self.density:4.3f}' if self._has_density else ' D=None'
out_str += '>'
return out_str
def __repr__(self):
return self.__str__()
[docs]
def __hash__(self):
'''Needed so Components can be dictionary keys'''
return id(self)
[docs]
def copy(self):
return copy.deepcopy(self)
[docs]
def __iter__(self):
'''Dummy iterator to mimic behavior of Mixture.'''
for name,component in [(self.name,self)]:
yield name,component
@property
def mass(self):
return self._mass
@mass.setter
def mass(self,value):
enforce_units(value,'mass')
self._mass = value
[docs]
def set_mass(self,value):
'''Setter for inline mass changes'''
component = self.copy()
component.mass = value
return component
@property
def volume(self):
if self._has_density:
return enforce_units(self._mass/self._density,'volume')
else:
return None
@volume.setter
def volume(self,value):
enforce_units(value,'volume')
if not self._has_density:
raise ValueError('Can\'t set volume without specifying density')
else:
self.mass = enforce_units(value*self._density,'mass')
[docs]
def set_volume(self,value):
'''Setter for inline volume changes'''
component = self.copy()
component.volume = value
return component
@property
def density(self):
return self._density
@density.setter
def density(self,value):
enforce_units(value,'density')
self._density = value
@property
def formula(self):
return self._formula
@formula.setter
def formula(self,value):
if value is None:
self._formula = None
else:
try:
self._formula = periodictable.formula(value)
except (ValueError,ParseException):
self._formula = None
@property
def moles(self):
if self._has_formula:
return self._mass/(self.formula.molecular_mass*units('g'))/AVOGADROS_NUMBER
else:
return None
@property
def sld(self):
if self._sld is not None:
return self._sld
elif self._has_formula and self._has_density:
self.formula.density = self.density.to('g/ml').magnitude
sld = self.formula.neutron_sld(wavelength=5.0)[0]
return sld*1e-6*units('angstrom^(-2)')
else:
return None
@sld.setter
def sld(self,value):
self._sld = value
@property
def is_solute(self):
return self.preptype==PrepType.Solute
@property
def is_solvent(self):
return self.preptype==PrepType.Solvent
@property
def _has_volume(self):
return (self._volume is not None)
@property
def _has_density(self):
return (self._density is not None)
@property
def _has_formula(self):
return (self._formula is not None)
@property
def _has_sld(self):
return ((self._sld is not None) or (self._has_formula and self._has_density))
def __add__(self,other):
if not (self.preptype==other.preptype):
raise ValueError(f'Can only add components of the same preptype. Not {self.preptype} and {other.preptype}')
if not (self.name == other.name):
raise ValueError(f'Can only add components of the same name. Not {self.name} and {other.name}')
if not (self.density == other.density):
raise ValueError(f'Density mismatch in component.__add__: {self.density} and {other.density}')
component = copy.deepcopy(self)
component.mass = enforce_units(component._mass + other._mass,'mass')
return component