""" Provide the ``Project`` class.
As of this writing (v.0.1.0) ``Project`` classes will be required for all
actions.
"""
# -- Imports -----------------------------------------------------------------
from datetime import datetime
from random import randint
import pint
units = pint.UnitRegistry()
# -- Project Class -----------------------------------------------------------
[docs]class Project(object):
""" Class to represent a new project.
"""
[docs] def __init__(self, unit_system, **kwargs):
"""
Args:
unit_system (str): The unit system for the project. Can only be
'English', or 'SI'.
Keyword Args:
project_id (int): The unique id for the project. If one is not
provided, a random 8-digit number will be assigned.
project_name (str): The project name. If none is entered it
defaults to "New Project".
date (timestamp): The date and time of the analysis. If it is not
provided, the current time when the object was instantiated
is stored.
"""
# Check for valid attributes
allowed_keys = ['project_id', 'project_name', 'date']
for key in kwargs:
if key not in allowed_keys:
raise AttributeError("'{}' is not a valid attribute. The "
"allowed attributes are: {}"
"".format(key, allowed_keys))
# Check for Unit System
if unit_system in ['English', 'SI']:
self.unit_system = unit_system
else:
raise ValueError("Unit system can only be 'English' or 'SI'.")
# Assign values
self.project_id = kwargs.get('project_id', randint(10e6, 10e7-1))
self.project_name = kwargs.get('project_name', 'New Project')
self.date = kwargs.get('date', datetime.now())
self.sp = None
self.pile = None
# -- A Helper Method to set units ----------------------------------------
[docs] def set_units(self, dim):
""" A private helper method that returns the Pint units to be attached
to a variable based on the set unit system and dimensionality (dim).
Since this is a private method, the stored values will not be shown
in the docstring. Refer to the `unit_dict` in the code.
Args:
dim (str): The dimensionality for the variable. For example, layer
height is 'length'.
Returns:
Pint units.
"""
unit_dict = {
'degrees': {'SI': units.degree, 'English': units.degree},
'length': {'SI': units.meter, 'English': units.feet},
'tuw': {'SI': units.kN / units.meter ** 3,
'English': (units.kip/1000) / units.feet ** 3},
'stress': {'SI': units.kN / units.meter ** 2,
'English': units.kip / units.feet ** 2},
'capacity': {'SI': units.kN, 'English': units.kip},
'pile_diameter': {'SI': units.cm, 'English': units.inches},
'pile_length': {'SI': units.meter, 'English': units.feet},
'pile_xarea': {'SI': units.cm ** 2, 'English': units.inches ** 2},
'pile_xarea_alt': {'SI': units.meter ** 2,
'English': units.feet ** 2},
'pile_side_area': {'SI': units.meter ** 2,
'English': units.feet ** 2},
}
return unit_dict[dim][self.unit_system]
# -- Method to attach a soil profile -------------------------------------
[docs] def attach_sp(self, obj):
""" Method that attaches a ``SoilProfile`` object to the ``Project``
object. The ``Project`` class supports adding **one** ``SoilProfile``
object. If you run the ``attach_sp`` more than once, it will replace
the previous ``SoilProfile`` object.
Args:
obj (class): A :class:`~edafos.soil.profile.SoilProfile` class.
Returns:
self
"""
# Type check
if str(type(obj)) == "<class 'edafos.soil.profile.SoilProfile'>":
self.sp = obj
else:
raise TypeError("Wrong input. Attach `SoilProfile` objects only.")
# Unit system check
# TODO: This needs more work for what happens when units are different
if self.unit_system != self.sp.unit_system:
raise AttributeError("Inconsistent unit systems.")
else:
pass
return self
# -- Method to attach a pile ---------------------------------------------
[docs] def attach_pile(self, obj):
""" Method that attaches a ``Pile`` object to the ``Project`` object.
The ``Project`` class supports adding **one** ``Pile`` object. If you
run the ``attach_pile`` more than once, it will replace the previous
``Pile`` object.
Args:
obj (class): A :class:`~edafos.deepfoundations.piles.Pile` class.
Returns:
self
"""
# Type check
if str(type(obj)) == "<class 'edafos.deepfoundations.piles.Pile'>":
self.pile = obj
else:
raise TypeError("Wrong input. Attach `Pile` objects only.")
# Unit system check
# TODO: This needs more work for what happens when units are different
if self.unit_system != self.pile.unit_system:
raise AttributeError("Inconsistent unit systems.")
else:
pass
# Check if the pile is longer than the soil profile
if self.sp is None:
pass
else:
if self.sp.layers['Depth'].max() < self.pile.pen_depth.magnitude:
raise ValueError("Pile penetration depth is larger than "
"total soil profile depth.")
else:
pass
return self
# -- Method for list of relevant z's -------------------------------------
[docs] def z_layer_pile(self):
""" Method that analyzes the defined soil layers, water table, pile
tapered sections (if any) and produces a list of depths, :math:`z`,
where there is a change in soil conditions or pile properties.
Returns:
list: A list of depths, :math:`z` (unitless).
"""
# Check if SoilProfile and Pile are attached
if (self.sp is None) or (self.pile is None):
raise ValueError("No SoilProfile or Pile attached to Project.")
else:
pass
# Fix for negative water table (offshore)
wt = self.sp.water_table.magnitude
if wt < 0:
wt = 0
# Compile list
z_list = self.sp.z_of_layers() + self.pile.z_of_pile() + [wt]
# Remove duplicates and sort
z_list = list(set(z_list))
z_list.sort()
return z_list
# -- Method for string representation ------------------------------------
def __str__(self):
return "Project ID: {0.project_id}\nProject Name: {0.project_name}" \
"\nDatetime: {0.date}\nUnit System: {0.unit_system}\n" \
"{1}\n" \
"Soil Profile WT: {0.sp.water_table}\n" \
"Pile Type: {0.pile.pile_type}".format(self, 12*'-')