"""main module with resize and validation functions"""
from __future__ import division
import math
import sys
from functools import wraps

from PIL import Image
from .imageexceptions import ImageSizeError

def validate(validator):
    """
    Return a decorator that validates arguments with provided `validator`
    function.

    This will also store the validator function as `func.validate`.
    The decorator returned by this function, can bypass the validator
    if `validate=False` is passed as argument otherwise the fucntion is
    called directly.

    The validator must raise an exception, if the function can not
    be called.
    """

    def decorator(func):
        """Bound decorator to a particular validator function"""

        @wraps(func)
        def wrapper(image, size, validate=True):
            if validate:
                validator(image, size)
            return func(image, size)
        return wrapper

    return decorator

def _is_big_enough(image, size):
    """Check that the image's size superior to `size`"""
    if (size[0] > image.size[0]) and (size[1] > image.size[1]):
        raise ImageSizeError(image.size, size)

def _width_is_big_enough(image, width):
    """Check that the image width is superior to `width`"""
    if width > image.size[0]:
        raise ImageSizeError(image.size[0], width)

def _height_is_big_enough(image, height):
    """Check that the image height is superior to `height`"""
    if height > image.size[1]:
        raise ImageSizeError(image.size[1], height)

@validate(_is_big_enough)
def resize_crop(image, size):
    """
    Crop the image with a centered rectangle of the specified size
    image:      a Pillow image instance
    size:       a list of two integers [width, height]
    """
    img_format = image.format
    image = image.copy()
    old_size = image.size
    left = (old_size[0] - size[0]) / 2
    top = (old_size[1] - size[1]) / 2
    right = old_size[0] - left
    bottom = old_size[1] - top
    rect = [int(math.ceil(x)) for x in (left, top, right, bottom)]
    left, top, right, bottom = rect
    crop = image.crop((left, top, right, bottom))
    crop.format = img_format
    return crop

@validate(_is_big_enough)
def resize_cover(image, size, resample=Image.LANCZOS):
    """
    Resize image according to size.
    image:      a Pillow image instance
    size:       a list of two integers [width, height]
    """
    img_format = image.format
    img = image.copy()
    img_size = img.size
    ratio = max(size[0] / img_size[0], size[1] / img_size[1])
    new_size = [
        int(math.ceil(img_size[0] * ratio)),
        int(math.ceil(img_size[1] * ratio))
    ]
    img = img.resize((new_size[0], new_size[1]), resample)
    img = resize_crop(img, size)
    img.format = img_format
    return img

def resize_contain(image, size, resample=Image.LANCZOS, bg_color=(255, 255, 255, 0)):
    """
    Resize image according to size.
    image:      a Pillow image instance
    size:       a list of two integers [width, height]
    """
    img_format = image.format
    img = image.copy()
    img.thumbnail((size[0], size[1]), resample)
    background = Image.new('RGBA', (size[0], size[1]), bg_color)
    img_position = (
        int(math.ceil((size[0] - img.size[0]) / 2)),
        int(math.ceil((size[1] - img.size[1]) / 2))
    )
    background.paste(img, img_position)
    background.format = img_format
    return background.convert('RGB')

@validate(_width_is_big_enough)
def resize_width(image, size, resample=Image.LANCZOS):
    """
    Resize image according to size.
    image:      a Pillow image instance
    size:       an integer or a list or tuple of two integers [width, height]
    """
    try:
        width = size[0]
    except:
        width = size
    img_format = image.format
    img = image.copy()
    img_size = img.size
    # If the origial image has already the good width, return it
    # fix issue #16
    if img_size[0] == width:
        return image
    new_height = int(math.ceil((width / img_size[0]) * img_size[1]))
    img.thumbnail((width, new_height), resample)
    img.format = img_format
    return img

@validate(_height_is_big_enough)
def resize_height(image, size, resample=Image.LANCZOS):
    """
    Resize image according to size.
    image:      a Pillow image instance
    size:       an integer or a list or tuple of two integers [width, height]
    """
    try:
        height = size[1]
    except:
        height = size
    img_format = image.format
    img = image.copy()
    img_size = img.size
    # If the origial image has already the good height, return it
    # fix issue #16
    if img_size[1] == height:
        return image
    new_width = int(math.ceil((height / img_size[1]) * img_size[0]))
    img.thumbnail((new_width, height), resample)
    img.format = img_format
    return img

def resize_thumbnail(image, size, resample=Image.LANCZOS):
    """
    Resize image according to size.
    image:      a Pillow image instance
    size:       a list of two integers [width, height]
    """

    img_format = image.format
    img = image.copy()
    img.thumbnail((size[0], size[1]), resample)
    img.format = img_format
    return img

def resize(method, *args, **kwargs):
    """
    Helper function to access one of the resize function.
    method:     one among 'crop', 'cover', 'contain', 'width', 'height' or 'thumbnail'
    image:      a Pillow image instance
    size:       a list or tuple of two integers [width, height]
    """
    if method not in ['crop',
                      'cover',
                      'contain',
                      'width',
                      'height',
                      'thumbnail']:
        raise ValueError(u"method argument should be one of \
            'crop', 'cover', 'contain', 'width', 'height' or 'thumbnail'")
    return getattr(sys.modules[__name__], 'resize_%s' % method)(*args, **kwargs)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from datetime import datetime
from django_importer.importers.csv_importer import CSVImporter
from tasks.models import Category, Task

# Equivalence for category slugs
category_map = {
    'Desenv': 'desenvolvimento',
    'Rede': 'rede',
    'Suporte': 'suporte',
}

class TaskImporter(CSVImporter):
    """
    CSV importer for tasks.
    """

    model = Task

    fields = ('category', 'date', 'start_time', 'end_time', 'duration',
              'description', 'template', 'temp_1', 'temp_2',
              'total_created', 'total_worked', 'total_closed',
              'total_to_test', 'total_open',
             )

    field_map = {
        'category': u'Categoria',

        'date': u'Date',
        'start_time': u'Inicio',
        'end_time': u'Fim',
        'duration': u'Duração',

        'description': u'Descrição',
        'template': u'Modelo',
        'temp_1': u'Temp1',
        'temp_2': u'Temp2',

        'total_created': u'Criadas',
        'total_worked': u'Trabalhadas',
        'total_closed': u'Fechadas',
        'total_to_test': u'To Test',
        'total_open': u'Open',
    }

    unique_fields = ('date', 'start_time')

    def parse_category(self, item, field_name, source_name):
        """
        Converts the text category to a tasks.Category instance.
        """
        # Get and checks for the corresponding slug
        slug = category_map.get(self.get_value(item, source_name), None)
        if not slug:
            return None
        # Load the category instance
        try:
            return Category.objects.get(slug=slug)
        except Category.DoesNotExist:
            pass

    def parse_date(self, item, field_name, source_name):
        """
        Converts the date in the format: Thu 03.

        As only the day is provided, tries to find the best match
        based on the current date, considering that dates are on
        the past.
        """
        # Get the current date
        now = datetime.now().date()
        # Get the date from the source
        val = self.get_value(item, source_name)
        week_day, day = val.split()
        day = int(day)
        # If the current date is minor than the item date
        # go back one month
        if now.day < day:
            if now.month == 1:
                now = now.replace(month=12, year=now.year-1)
            else:
                now = now.replace(month=now.month-1)
        # Finally, replace the source day in the current date
        # and return
        now = now.replace(day=day)
        return now

    def parse_totals(self, item, field_name, source_name):
        """
        Parse numeric fields.
        """
        val = self.get_value(item, source_name)
        try:
            return int(val)
        except:
            return 0

    # Reuse numeric parser method
    parse_total_created = parse_totals
    parse_total_worked = parse_totals
    parse_total_closed = parse_totals
    parse_total_to_test = parse_totals
    parse_total_open = parse_totals


# -*- coding: utf-8 -*-

from django_importer.importers.base import Importer
from django.utils.encoding import smart_str

try:
    from django.utils.encoding import force_text
except ImportError:
    from django.utils.encoding import force_unicode as force_text

# Try to import the best match for `ElementTree`.
# Prioritizes the C port `cElementTree` for *much* better performance.
# Starting from Python 2.5, `cElementTree` is found on `xml.etree`
try:
    from xml.etree import cElementTree as ElementTree
except ImportError:
    try:
        import cElementTree as ElementTree
    except ImportError:
        from elementtree import ElementTree

class XMLImporter(Importer):
    """
    Import models from a local XML file. Requires `ElementTree`.
    """

    # The name of the XML node that represents an item
    item_tag_name = 'item'

    def load(self, source):
        """
        Doesn't load anything, just changes the source property,
        `ElementTree` is capable of using a file stream or path,
        so the source type doesn't matter.
        """
        self.source = source
        self.loaded = True

    def get_items(self):
        """
        Iterator of the list of items in the XML source.
        """
        # Use `iterparse`, it's more efficient, specially for big files
        for event, item in ElementTree.iterparse(self.source):
            if item.tag == self.item_tag_name:
                yield item
                # Releases the item from memory
                item.clear()

    def get_value(self, item, source_name):
        """
        This method receives an item from the source and a source name,
        and returns the text content for the `source_name` node.
        """
        return force_text(smart_str(item.findtext(source_name))).strip()


# -*- coding: utf-8 -*-

"""
= django-importer =

Importers for Django models.
Developed and maintained by Enrico Batista da Luz <rico.bl@gmail.com>

To use an existent importer, configure the class properties:

  * `model`: the model to create instances from the data.
  * `fields`: which fields in your model you want to import from the source.
  * `field_map`: the mapping of your field models to the source fields.
  * `unique_fields`: fields that identify an item as unique.

To create your own importer for a different source type, extend `ImporterBase`
and all methods with `NotImplementedError`. Or extend one of the available
importers.
"""

from traceback import format_exception
import sys

class Importer(object):
    """
    Base class to create importers for different sources.
    """

    fields = ()
    field_map = {}
    unique_fields = ()
    model = None

    def __init__(self, source=None):
        self.source = source
        self.loaded = False
        self.errors = []

    def save_error(self, data, exception_info):
        """
        Saves an error in the error list.
        """
        # TODO: what to do with errors? Let it flow? Write to a log file?
        self.errors.append({'data': data,
                            'exception': ''.join(format_exception(*exception_info)),
                            })

    def parse(self):
        """
        Parses all data from the source, saving model instances.
        """
        # Checks if the source is loaded
        if not self.loaded:
            self.load(self.source)

        for item in self.get_items():
            # Parse the fields from the source into a dict
            data = self.parse_item(item)
            # Get the instance from the DB, or a new one
            instance = self.get_instance(data)
            # Feed instance with data
            self.feed_instance(data, instance)
            # Try to save the instance or keep the error
            try:
                self.save_item(item, data, instance)
            except Exception as e:
                self.save_error(data, sys.exc_info())

        # Unload the source
        self.unload()

    def parse_item(self, item):
        """
        Receives an item and returns a dictionary of field values.
        """
        # Create a dictionary from values for each field
        parsed_data = {}

        for field_name in self.fields:
            # A field-name may be mapped to another identifier on the source,
            # it could be a XML path or a CSV column name / position.
            # Defaults to the field-name itself.
            source_name = self.field_map.get(field_name, field_name)

            # Uses a custom method "parse_%(field_name)"
            # or get the value from the item
            parse = getattr(self, 'parse_%s' % field_name, None)
            if parse:
                value = parse(item, field_name, source_name)
            else:
                value = self.get_value(item, source_name)

            # Add the value to the parsed data
            parsed_data[field_name] = value
        return parsed_data

    def get_instance(self, data):
        """
        Get an item from the database or an empty one if not found.
        """
        # Get unique fields
        unique_fields = self.unique_fields

        # If there are no unique fields option, all items are new
        if not unique_fields:
            return self.model()

        # Build the filter
        filter = dict([(f, data[f]) for f in unique_fields])

        # Get the instance from the DB or use a new instance
        try:
            instance = self.model._default_manager.get(**filter)
        except self.model.DoesNotExist:
            return self.model()

        return instance

    def feed_instance(self, data, instance):
        """
        Feeds a model instance using parsed data (usually from `parse_item`).
        """
        for prop, val in data.items():
            setattr(instance, prop, val)
        return instance

    def save_item(self, item, data, instance, commit=True):
        """
        Saves a model instance to the database.
        """
        if commit:
            instance.save()
        return instance

    def unload(self):
        """
        Unloads the source file.
        Useful to close open files and free resources.

        Not a required method by subclasses.

        Rembember to unset the ``loaded`` when extending this method.
        """
        self.loaded = False

    ### ABSTRACT METHODS #############

    def load(self, source):
        """
        Load data from the source.

        Must be implemented on subclasses or will raise `NotImplementedError`.
        """
        raise NotImplementedError

    def get_items(self):
        """
        Get the list of items for the current source.
        Must return an iterable.

        Must be implemented on subclasses or will raise `NotImplementedError`.
        """
        raise NotImplementedError

    def get_value(self, item, source_name):
        """
        Receives an item from the source and a source name,
        and must return the value for the field.

        Must be implemented on subclasses or will raise `NotImplementedError`.
        """
        raise NotImplementedError


# -*- coding: utf-8 -*-

import StringIO
import gzip
import urllib2

def download_file(url, dest):
    """
    Downloads a HTTP resource from `url` and save to `dest`.

    Capable of dealing with Gzip compressed content.
    """

    # Create the HTTP request
    request = urllib2.Request(url)

    # Add the header to accept gzip encoding
    request.add_header('Accept-encoding', 'gzip')

    # Open the request
    opener = urllib2.build_opener()
    response = opener.open(request)

    # Retrieve data
    data = response.read()

    # If the data is compressed, put the data in a stream and decompress
    if response.headers.get('content-encoding', '') == 'gzip':
        stream = StringIO.StringIO(data)
        gzipper = gzip.GzipFile(fileobj=stream)
        data = gzipper.read()

    # Write to a file
    f = open(dest, 'wb')
    f.write(data)
    f.close()


# -*- coding: utf-8 -*-

import csv
from django_importer.importers.base import Importer
from django.utils.encoding import smart_str, force_unicode

def convert_string(s):
    return force_unicode(smart_str(s)).strip()

class CSVImporter(Importer):
    """
    Import models from a local CSV file. Requires `csv` module.
    """

    def load(self, source):
        """
        Opens the source file.
        """
        self.source = open(self.source, 'rb')
        self.loaded = True

    def unload(self):
        """
        Closes the input file to free resources.
        """
        self.source.close()
        self.loaded = False

    def get_items(self):
        """
        Iterator to read the rows of the CSV file.
        """
        # Get the csv reader
        reader = csv.reader(self.source)
        # Get the headers from the first line
        headers = reader.next()
        # Read each line yielding a dictionary mapping
        # the column headers to the row values
        for row in reader:
            # Skip empty rows
            if not row:
                continue
            yield dict(zip(headers, row))

    def get_value(self, item, source_name):
        """
        This method receives an item from the source and a source name,
        and returns the text content for the `source_name` node.
        """
        val = item.get(source_name.encode('utf-8'), None)
        if val is not None:
            val = convert_string(val)
        return val


"""
Setup script for PyPi
"""
import codecs
import re
from setuptools import setup

# Get the long description from the relevant file
with codecs.open('README.rst', encoding='utf-8') as f:
    long_description = f.read()

# Open the package file so we can read the meta data.
with codecs.open('random_useragent.py', encoding='utf-8') as f:
    package_file = f.read()

def get_package_meta(meta_name):
    """Return value of variable set in the package where said variable is
    named in the Python meta format `__<meta_name>__`.
    """
    regex = "__{0}__ = ['\"]([^'\"]+)['\"]".format(meta_name)
    return re.search(regex, package_file).group(1)

version = get_package_meta('version')
author = get_package_meta('author')
email = get_package_meta('email')
license = get_package_meta('license')

setup(
    name='scrapy-random-useragent',
    version=version,

    description='Scrapy Middleware to set a random User-Agent for every Request.',
    long_description=long_description,

    author=author,
    author_email=email,
    url='https://github.com/cnu/scrapy-random-useragent',

    license=license,

    py_modules=['random_useragent'],
    platforms=['Any'],

    keywords="scrapy random user-agent ",
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Environment :: Console',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Framework :: Scrapy',
    ]
)


import json
import os
import subprocess
import tarfile
import tempfile
import uuid
from io import FileIO
from typing import List

import redis

VERSION = '3.1.2'

SANDBOX_HOME_DIR_NAME = '/home/autograder'
SANDBOX_WORKING_DIR_NAME = os.path.join(SANDBOX_HOME_DIR_NAME, 'working_dir')
SANDBOX_USERNAME = 'autograder'
SANDBOX_DOCKER_IMAGE = os.environ.get('SANDBOX_DOCKER_IMAGE',
                                      'jameslp/autograder-sandbox:{}'.format(VERSION))

class AutograderSandbox:
    """
    This class wraps Docker functionality to provide an interface for
    running untrusted programs in a secure, isolated environment.

    Docker documentation and installation instructions can be
    found at: https://www.docker.com/

    Instances of this class are intended to be used with a context
    manager. The underlying docker container to be used is created and
    destroyed when the context manager is entered and exited,
    respectively.
    """

    def __init__(self, name: str=None,
                 docker_image: str=SANDBOX_DOCKER_IMAGE,
                 allow_network_access: bool=False,
                 environment_variables: dict=None,
                 container_create_timeout: int=None,
                 debug=False) -> None:
        """
        :param name: A human-readable name that can be used to identify
            this sandbox instance. This value must be unique across all
            sandbox instances, otherwise starting the sandbox will fail.
            If no value is specified, a random name will be generated
            automatically.

        :param docker_image: The name of the docker image to create the
            sandbox from. Note that in order to function properly, all
            custom docker images must extend jameslp/autograder-sandbox.
            This value takes precedence over the value of the
            environment variable SANDBOX_DOCKER_IMAGE.

        :param allow_network_access: When True, programs running inside
            the sandbox will have unrestricted access to external
            IP addresses. When False, programs will not be able
            to contact any external IPs.

        :param environment_variables: A dictionary of (variable_name:
            value) pairs that should be set as environment variables
            inside the sandbox.

        :param container_create_timeout: A time limit to be placed on
            creating the underlying Docker container for this sandbox.
            If the time limit is exceeded, subprocess.CalledProcessError
            will be raised. A value of None indicates no time limit.

        :param debug: Whether to print additional debugging information.
        """
        if name is None:
            self._name = 'sandbox-{}'.format(uuid.uuid4().hex)
        else:
            self._name = name

        self._docker_image = docker_image
        self._linux_uid = _get_next_linux_uid()
        self._allow_network_access = allow_network_access
        self._environment_variables = environment_variables
        self._is_running = False
        self._container_create_timeout = container_create_timeout
        self.debug = debug

    def __enter__(self):
        self._create_and_start()
        return self

    def __exit__(self, *args):
        self._destroy()

    def reset(self) -> None:
        """
        Destroys, re-creates, and restarts the sandbox. As a side
        effect, this will effectively kill any processes running inside
        the sandbox and reset the sandbox's filesystem.
        """
        self._destroy()
        self._create_and_start()

    def restart(self):
        """
        Restarts the sandbox without destroying it. As a side effect,
        this will kill any processes running inside the sandbox.

        IMPORTANT: It is strongly recommended that you call this method
        after a command run by run_command times out.
        """
        self._stop()
        subprocess.check_call(['docker', 'start', self.name])

    def _create_and_start(self):
        create_args = [
            'docker', 'run',
            '--name=' + self.name,
            '-i',  # Run in interactive mode (for input redirection)
            '-t',  # Allocate psuedo tty
            '-d',  # Detached
        ]

        if not self.allow_network_access:
            # Create the container without a network stack.
            create_args += ['--net', 'none']

        if self.environment_variables:
            for key, value in self.environment_variables.items():
                create_args += [
                    '-e', "{}={}".format(key, value)
                ]

        create_args.append(self.docker_image)  # Image to use

        subprocess.check_call(create_args,
                              timeout=self._container_create_timeout)
        try:
            subprocess.run(
                ['docker', 'exec', '-i', self.name, 'usermod', '-u',
                 str(self._linux_uid), SANDBOX_USERNAME],
                check=True)
        except subprocess.CalledProcessError as e:
            if self.debug:
                print(e.stdout)
                print(e.stderr)

            self._destroy()
            raise

        self._is_running = True

    def _destroy(self):
        self._stop()
        subprocess.check_call(['docker', 'rm', self.name])
        self._is_running = False

    def _stop(self):
        subprocess.check_call(['docker', 'stop', '--time', '1', self.name])

    @property
    def name(self) -> str:
        """
        The name used to identify this sandbox. (Read only)
        """
        return self._name

    @property
    def docker_image(self) -> str:
        """
        The name of the docker image to create the sandbox from.
        """
        return self._docker_image

    @property
    def allow_network_access(self) -> bool:
        """
        Whether network access is allowed by this sandbox.
        If an attempt to set this value is made while the sandbox is
        running, ValueError will be raised.
        """
        return self._allow_network_access

    @allow_network_access.setter
    def allow_network_access(self, value: bool):
        """
        Raises ValueError if this sandbox instance is currently running.
        """
        if self._is_running:
            raise ValueError(
                "Cannot change network access settings on a running sandbox")

        self._allow_network_access = value

    @property
    def environment_variables(self) -> dict:
        """
        A dictionary of environment variables to be set inside the
        sandbox (Read only).
        """
        if not self._environment_variables:
            return {}

        return dict(self._environment_variables)

    def run_command(self,
                    args: List[str],
                    max_num_processes: int=None,
                    max_stack_size: int=None,
                    max_virtual_memory: int=None,
                    as_root: bool=False,
                    stdin: FileIO=None,
                    timeout: int=None,
                    check: bool=False,
                    truncate_stdout: int=None,
                    truncate_stderr: int=None) -> 'CompletedCommand':
        """
        Runs a command inside the sandbox and returns the results.

        :param args: A list of strings that specify which command should
            be run inside the sandbox.

        :param max_num_processes: The maximum number of processes the
            command is allowed to spawn.

        :param max_stack_size: The maximum stack size, in bytes, allowed
            for the command.

        :param max_virtual_memory: The maximum amount of memory, in
            bytes, allowed for the command.

        :param as_root: Whether to run the command as a root user.

        :param stdin: A file object to be redirected as input to the
            command's stdin. If this is None, /dev/null is sent to the
            command's stdin.

        :param timeout: The time limit for the command.

        :param check: Causes CalledProcessError to be raised if the
            command exits nonzero or times out.

        :param truncate_stdout: When not None, stdout from the command
            will be truncated after this many bytes.

        :param truncate_stderr: When not None, stderr from the command
            will be truncated after this many bytes.
        """
        cmd = ['docker', 'exec', '-i', self.name, 'cmd_runner.py']

        if stdin is None:
            cmd.append('--stdin_devnull')

        if max_num_processes is not None:
            cmd += ['--max_num_processes', str(max_num_processes)]

        if max_stack_size is not None:
            cmd += ['--max_stack_size', str(max_stack_size)]

        if max_virtual_memory is not None:
            cmd += ['--max_virtual_memory', str(max_virtual_memory)]

        if timeout is not None:
            cmd += ['--timeout', str(timeout)]

        if truncate_stdout is not None:
            cmd += ['--truncate_stdout', str(truncate_stdout)]

        if truncate_stderr is not None:
            cmd += ['--truncate_stderr', str(truncate_stderr)]

        if not as_root:
            cmd += ['--linux_user_id', str(self._linux_uid)]

        cmd += args

        if self.debug:
            print('running: {}'.format(cmd), flush=True)

        with tempfile.TemporaryFile() as f:
            try:
                subprocess.run(cmd, stdin=stdin, stdout=f, stderr=subprocess.PIPE, check=True)
                f.seek(0)
                json_len = int(f.readline().decode().rstrip())
                results_json = json.loads(f.read(json_len).decode())

                stdout_len = int(f.readline().decode().rstrip())
                stdout = tempfile.NamedTemporaryFile()
                stdout.write(f.read(stdout_len))
                stdout.seek(0)

                stderr_len = int(f.readline().decode().rstrip())
                stderr = tempfile.NamedTemporaryFile()
                stderr.write(f.read(stderr_len))
                stderr.seek(0)

                result = CompletedCommand(return_code=results_json['return_code'],
                                          timed_out=results_json['timed_out'],
                                          stdout=stdout,
                                          stderr=stderr,
                                          stdout_truncated=results_json['stdout_truncated'],
                                          stderr_truncated=results_json['stderr_truncated'])

                if (result.return_code != 0 or results_json['timed_out']) and check:
                    raise subprocess.CalledProcessError(
                        result.return_code, cmd,
                        output=result.stdout, stderr=result.stderr)

                return result
            except subprocess.CalledProcessError as e:
                f.seek(0)
                print(f.read())
                print(e.stderr)
                raise

    def add_files(self, *filenames: str, owner: str=SANDBOX_USERNAME, read_only: bool=False):
        """
        Copies the specified files into the working directory of this
        sandbox.
        The filenames specified can be absolute paths or relative paths
        to the current working directory.

        :param owner: The name of a user who should be granted ownership of
            the newly added files.
            Must be either autograder_sandbox.SANDBOX_USERNAME or 'root',
            otherwise ValueError will be raised.
        :param read_only: If true, the new files' permissions will be set to
            read-only.
        """
        if owner != SANDBOX_USERNAME and owner != 'root':
            raise ValueError('Invalid value for parameter "owner": {}'.format(owner))

        with tempfile.TemporaryFile() as f, \
                tarfile.TarFile(fileobj=f, mode='w') as tar_file:
            for filename in filenames:
                tar_file.add(filename, arcname=os.path.basename(filename))

            f.seek(0)
            subprocess.check_call(
                ['docker', 'cp', '-',
                 self.name + ':' + SANDBOX_WORKING_DIR_NAME],
                stdin=f)

            file_basenames = [os.path.basename(filename) for filename in filenames]
            if owner == SANDBOX_USERNAME:
                self._chown_files(file_basenames)

            if read_only:
                chmod_cmd = ['chmod', '444'] + file_basenames
                self.run_command(chmod_cmd, as_root=True)

    def add_and_rename_file(self, filename: str, new_filename: str) -> None:
        """
        Copies the specified file into the working directory of this
        sandbox and renames it to new_filename.
        """
        dest = os.path.join(
            self.name + ':' + SANDBOX_WORKING_DIR_NAME,
            new_filename)
        subprocess.check_call(['docker', 'cp', filename, dest])
        self._chown_files([new_filename])

    def _chown_files(self, filenames):
        chown_cmd = [
            'chown', '{}:{}'.format(SANDBOX_USERNAME, SANDBOX_USERNAME)]
        chown_cmd += filenames
        self.run_command(chown_cmd, as_root=True)

class CompletedCommand:
    def __init__(self, return_code: int, stdout: FileIO, stderr: FileIO, timed_out: bool,
                 stdout_truncated: bool, stderr_truncated: bool):
        """
        :param return_code: The return code of the command,
            or None if the command timed out.
        :param stdout: A file object containing the
            stdout content of the command.
        :param stderr: A file object containing the
            stderr content of the command.
        :param timed_out: Whether the command exceeded the time limit.
        :param stdout_truncated: Whether stdout was truncated.
        :param stderr_truncated: Whether stderr was truncated.
        """
        self.return_code = return_code
        self.stdout = stdout
        self.stderr = stderr
        self.timed_out = timed_out
        self.stdout_truncated = stdout_truncated
        self.stderr_truncated = stderr_truncated

_REDIS_SETTINGS = {
    'host': os.environ.get('AG_REDIS_HOST', 'localhost'),
    'port': os.environ.get('AG_REDIS_PORT', '6379')
}

_NEXT_UID_KEY = 'sandbox_next_uid'

def _get_next_linux_uid():
    redis_conn = redis.StrictRedis(**_REDIS_SETTINGS)
    redis_conn.setnx('sandbox_next_uid', 2000)
    return redis_conn.incr(_NEXT_UID_KEY)


from uw_canvas import Canvas
from uw_canvas.courses import Courses, COURSES_API
from uw_canvas.sections import SECTIONS_API
from uw_canvas.users import USERS_API
from uw_canvas.models import CanvasEnrollment
import re

class Enrollments(Canvas):
    def get_enrollments_for_course(self, course_id, params={}):
        """
        Return a list of all enrollments for the passed course_id.

        https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index
        """
        url = COURSES_API.format(course_id) + "/enrollments"

        enrollments = []
        for datum in self._get_paged_resource(url, params=params):
            enrollments.append(CanvasEnrollment(data=datum))

        return enrollments

    def get_enrollments_for_course_by_sis_id(self, sis_course_id, params={}):
        """
        Return a list of all enrollments for the passed course sis id.
        """
        return self.get_enrollments_for_course(
            self._sis_id(sis_course_id, sis_field="course"), params)

    def get_enrollments_for_section(self, section_id, params={}):
        """
        Return a list of all enrollments for the passed section_id.

        https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index
        """
        url = SECTIONS_API.format(section_id) + "/enrollments"

        enrollments = []
        for datum in self._get_paged_resource(url, params=params):
            enrollments.append(CanvasEnrollment(data=datum))

        return enrollments

    def get_enrollments_for_section_by_sis_id(self, sis_section_id, params={}):
        """
        Return a list of all enrollments for the passed section sis id.
        """
        return self.get_enrollments_for_section(
            self._sis_id(sis_section_id, sis_field="section"), params)

    def get_enrollments_for_regid(self, regid, params={},
                                  include_courses=True):
        """
        Return a list of enrollments for the passed user regid.

        https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.index
        """
        sis_user_id = self._sis_id(regid, sis_field="user")
        url = USERS_API.format(sis_user_id) + "/enrollments"

        courses = Courses() if include_courses else None

        enrollments = []
        for datum in self._get_paged_resource(url, params=params):
            enrollment = CanvasEnrollment(data=datum)
            if include_courses:
                course_id = datum["course_id"]
                course = courses.get_course(course_id)

                if course.sis_course_id is not None:
                    enrollment.course = course
                    # the following 3 lines are not removed
                    # to be backward compatible.
                    enrollment.course_url = course.course_url
                    enrollment.course_name = course.name
                    enrollment.sis_course_id = course.sis_course_id
            else:
                enrollment.course_url = re.sub(
                    r'/users/\d+$', '', enrollment.html_url)

            enrollments.append(enrollment)
        return enrollments

    def enroll_user(self, course_id, user_id, enrollment_type, params=None):
        """
        Enroll a user into a course.

        https://canvas.instructure.com/doc/api/enrollments.html#method.enrollments_api.create
        """
        url = COURSES_API.format(course_id) + "/enrollments"

        if not params:
            params = {}

        params["user_id"] = user_id
        params["type"] = enrollment_type

        data = self._post_resource(url, {"enrollment": params})
        return CanvasEnrollment(data=data)

    def enroll_user_in_course(self, course_id, user_id, enrollment_type,
                              course_section_id=None, role_id=None,
                              status="active"):
        params = {
            "user_id": user_id,
            "type": enrollment_type,
            "enrollment_state": status
        }

        if course_section_id:
            params['course_section_id'] = course_section_id

        if role_id:
            params['role_id'] = role_id

        return self.enroll_user(course_id, user_id, enrollment_type, params)


import numpy as np

from geofound.output import log
from geofound.exceptions import DesignError
from geofound import models
import sfsimodels as sm

def capacity_vesics_1975(sl, fd, h_l=0, h_b=0, vertical_load=1, slope=0, base_tilt=0, verbose=0, gwl=1e6, **kwargs):
    """
    Calculates the foundation capacity according Vesics(1975)
    #Gunaratne, Manjriker. 2006. "Spread Footings: Analysis and Design."
    Ref: http://geo.cv.nctu.edu.tw/foundation/download/
                            BearingCapacityOfFoundations.pdf

    :param sl: Soil object
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param slope: ground slope
    :param base_tilt: The slope of the underside of the foundation
    :param verbose: verbosity
    :return: ultimate bearing stress
    """

    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    area_foundation = fd.length * fd.width
    c_a = 0.6 - 1.0 * sl.cohesion

    horizontal_load = np.sqrt(h_l ** 2 + h_b ** 2)

    fd.nq_factor = ((np.tan(np.pi / 4 + sl.phi_r / 2)) ** 2 * np.exp(np.pi * np.tan(sl.phi_r)))
    if sl.phi_r == 0:
        fd.nc_factor = 5.14
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)
    fd.ng_factor = 2.0 * (fd.nq_factor + 1) * np.tan(sl.phi_r)

    # shape factors:
    s_c = 1.0 + fd.nq_factor / fd.nc_factor * fd.width / fd.length
    s_q = 1 + fd.width / fd.length * np.tan(sl.phi_r)
    s_g = max(1.0 - 0.4 * fd.width / fd.length, 0.6)  # add limit of 0.6 based on Vesic
    # depth factors:
    if fd.depth / fd.width > 1:
        k = np.arctan(fd.depth / fd.width)
    else:
        k = fd.depth / fd.width
    d_c = 1 + 0.4 * k
    d_q = 1 + 2 * np.tan(sl.phi_r) * (1 - np.sin(sl.phi_r)) ** 2 * k
    d_g = 1.0

    # load inclination factors
    m__b = (2.0 + fd.width / fd.length) / (1 + fd.width / fd.length)
    m_l = (2.0 + fd.length / fd.width) / (1 + fd.length / fd.width)
    m = np.sqrt(m__b ** 2 + m_l ** 2)

    if sl.phi_r == 0:
        i_q = 1.0
        i_g = 1.0
    else:
        i_q = (1.0 - horizontal_load / (vertical_load + area_foundation *
                                        c_a / np.tan(sl.phi_r))) ** m
        i_g = (1.0 - horizontal_load / (vertical_load + area_foundation *
                                        c_a / np.tan(sl.phi_r))) ** (m + 1)
    i_c = i_q - (1 - i_q) / (fd.nq_factor - 1)
    check_i_c = 1 - m * horizontal_load / (area_foundation * c_a * fd.nc_factor)
    if abs(check_i_c - i_c) / i_c > 0.001:
        raise DesignError

    # ground slope factors:
    if sl.phi_r == 0:
        # g_c = slope / 5.14
        g_c = i_q
    else:
        g_c = i_q - (1 - i_q) / (5.14 * np.tan(sl.phi_r))
    g_q = (1.0 - np.tan(slope)) ** 2
    g_g = g_q

    # tilted base factors
    if sl.phi_r == 0:
        b_c = g_c
    else:
        b_c = 1 - 2 * base_tilt / (5.14 * np.tan(sl.phi_r))
    b_q = (1.0 - base_tilt * np.tan(sl.phi_r)) ** 2
    b_g = b_q

    # stress at footing base:

    if gwl == 0:
        q_d = sl.unit_eff_weight * fd.depth
        unit_weight = sl.unit_bouy_weight

    elif gwl > 0 and gwl < fd.depth:
        q_d = (sl.unit_dry_weight * gwl) + (sl.unit_bouy_weight * (fd.depth - gwl))
        unit_weight = sl.unit_bouy_weight

    elif gwl >= fd.depth and gwl <= fd.depth + fd.width:
        sl.average_unit_bouy_weight = sl.unit_bouy_weight + (
            ((gwl - fd.depth) / fd.width) * (sl.unit_dry_weight - sl.unit_bouy_weight))
        q_d = sl.unit_dry_weight * fd.depth
        unit_weight = sl.average_unit_bouy_weight

    elif gwl > fd.depth + fd.width:
        q_d = sl.unit_dry_weight * fd.depth
        unit_weight = sl.unit_dry_weight

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("N_qV: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("d_c: ", d_c)
        log("d_q: ", d_q)
        log("d_g: ", d_g)
        log("i_c: ", i_c)
        log("i_q: ", i_q)
        log("i_g: ", i_g)
        log("g_c: ", g_c)
        log("g_q: ", g_q)
        log("g_g: ", g_g)
        log("b_c: ", b_c)
        log("b_q: ", b_q)
        log("b_g: ", b_g)
        log("q_d: ", q_d)

    # Capacity
    fd.q_ult = (sl.cohesion * fd.nc_factor * s_c * d_c * i_c * g_c * b_c +
                q_d * fd.nq_factor * s_q * d_q * i_q * g_q * b_q +
                0.5 * fd.width * unit_weight *
                fd.ng_factor * s_g * d_g * i_g * g_g * b_g)

    if verbose:
        log("qult: ", fd.q_ult)
    return fd.q_ult

def capacity_terzaghi_1943(sl, fd, round_footing=False, verbose=0, **kwargs):
    """
    Calculates the foundation capacity according Terzaghi (1943)
    Ref: http://geo.cv.nctu.edu.tw/foundation/
    download/BearingCapacityOfFoundations.pdf

    :param sl: Soil object
    :param fd: Foundation object
    :param round_footing: if True, then foundation is round
    :param verbose: verbosity
    :return: ultimate bearing stress
    Note: the shape factor of 1.3 is used for aspect ratio > 6
    """
    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    a02 = ((np.exp(np.pi * (0.75 - sl.phi / 360) * np.tan(sl.phi_r))) ** 2)
    a0_check = (np.exp((270 - sl.phi) / 180 * np.pi * np.tan(sl.phi_r)))
    if (a02 - a0_check) / a02 > 0.001:
        raise DesignError

    fd.nq_factor = (a02 / (2 * (np.cos((45 + sl.phi / 2) * np.pi / 180)) ** 2))
    fd.ng_factor = (2 * (fd.nq_factor + 1) * np.tan(sl.phi_r) / (1 + 0.4 * np.sin(4 * sl.phi_r)))
    if sl.phi_r == 0:
        fd.nc_factor = 5.7
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)

    # shape factors:
    if round_footing:
        s_c = 1.3
        s_g = 0.6
    elif fd.length / fd.width < 5:
        s_c = 1.3
        s_g = 0.8
    else:
        s_c = 1.0
        s_g = 1.0
    s_q = 1.0

    # stress at footing base:
    q_d = sl.unit_dry_weight * fd.depth

    # Capacity
    fd.q_ult = (sl.cohesion * fd.nc_factor * s_c + q_d * fd.nq_factor * s_q + 0.5 * fd.width *
                sl.unit_dry_weight * fd.ng_factor * s_g)

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("qult: ", fd.q_ult)
    return fd.q_ult

def capacity_hansen_1970(sl, fd, h_l=0, h_b=0, vertical_load=1, slope=0, base_tilt=0, verbose=0, **kwargs):
    """
    Calculates the foundation capacity according Hansen (1970)
    Ref: http://bestengineeringprojects.com/civil-projects/
    hansens-bearing-capacity-theory/

    :param sl: Soil object
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param slope: ground slope
    :param base_tilt: The slope of the underside of the foundation
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    area_foundation = fd.length * fd.width
    horizontal_load = np.sqrt(h_l ** 2 + h_b ** 2)
    c_a = 0.6 - 1.0 * sl.cohesion

    fd.nq_factor = ((np.tan(np.pi / 4 + sl.phi_r / 2)) ** 2 * np.exp(np.pi * np.tan(sl.phi_r)))
    if sl.phi_r == 0:
        fd.nc_factor = 5.14
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)
    fd.ng_factor = 1.5 * (fd.nq_factor - 1) * np.tan(sl.phi_r)

    # shape factors
    if sl.phi_r == 0:
        s_c = 0.2 * fd.width / fd.length
    else:
        s_c = 1.0 + fd.nq_factor / fd.nc_factor * fd.width / fd.length

    s_q = 1.0 + fd.width / fd.length * np.sin(sl.phi_r)
    s_g = 1.0 - 0.4 * fd.width / fd.length

    # depth factors:
    if fd.depth / fd.width > 1:
        k = np.arctan(fd.depth / fd.width)
    else:
        k = fd.depth / fd.width
    d_c = 1 + 0.4 * k
    if sl.phi == 0:
        d_c = 0.4 * k
    d_q = 1 + 2 * np.tan(sl.phi_r) * (1 - np.sin(sl.phi_r)) ** 2 * k
    d_g = 1.0

    # incline load factors:
    if sl.phi_r == 0:
        i_q = 1.0
        i_c = 0.5 - 0.5 * np.sqrt(1 - horizontal_load / area_foundation * c_a)
        i_g = 1.0
    else:
        i_q = ((1.0 - 0.5 * horizontal_load /
                (vertical_load + area_foundation * c_a / np.tan(sl.phi_r))) ** 5)
        i_c = i_q - (1 - i_q) / (fd.nq_factor - 1)
        i_g = ((1 - (0.7 * horizontal_load) /
                (vertical_load + area_foundation * c_a / np.tan(sl.phi_r))) ** 5)

    # slope factors:
    if sl.phi_r == 0:
        g_c = (slope / np.pi * 180) / 147
    else:
        g_c = 1.0 - (slope / np.pi * 180) / 147
    g_q = 1 - 0.5 * np.tan(slope) ** 5
    g_g = g_q

    # base tilt factors:
    if sl.phi_r == 0:
        b_c = (base_tilt / np.pi * 180) / 147
    else:
        b_c = 1.0 - (base_tilt / np.pi * 180) / 147
    b_q = (np.exp(-0.0349 * (base_tilt / np.pi * 180) * np.tan(sl.phi_r)))
    b_g = (np.exp(-0.0471 * (base_tilt / np.pi * 180) * np.tan(sl.phi_r)))

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("d_c: ", d_c)
        log("d_q: ", d_q)
        log("d_g: ", d_g)
        log("i_c: ", i_c)
        log("i_q: ", i_q)
        log("i_g: ", i_g)
        log("g_c: ", g_c)
        log("g_q: ", g_q)
        log("g_g: ", g_g)
        log("b_c: ", b_c)
        log("b_q: ", b_q)
        log("b_g: ", b_g)

    # stress at footing base:
    q_d = sl.unit_dry_weight * fd.depth

    # Capacity
    if sl.phi_r == 0:
        fd.q_ult = (sl.cohesion * fd.nc_factor *
                    (1 + s_c + d_c - i_c - g_c - b_c) + q_d)
    else:
        fd.q_ult = (sl.cohesion * fd.nc_factor *
                    s_c * d_c * i_c * g_c * b_c +
                    q_d * fd.nq_factor * s_q * d_q * i_q * g_q * b_q +
                    0.5 * fd.width * sl.unit_dry_weight *
                    fd.ng_factor * s_g * d_g * i_g * g_g * b_g)

def capacity_meyerhof_1963(sl, fd, gwl=1e6, h_l=0, h_b=0, vertical_load=1, verbose=0, **kwargs):
    """
    Calculates the foundation capacity according Meyerhoff (1963)
    http://www.engs-comp.com/meyerhof/index.shtml

    :param sl: Soil object
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    horizontal_load = np.sqrt(h_l ** 2 + h_b ** 2)

    fd.nq_factor = ((np.tan(np.pi / 4 + sl.phi_r / 2)) ** 2 *
                    np.exp(np.pi * np.tan(sl.phi_r)))
    if sl.phi_r == 0:
        fd.nc_factor = 5.14
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)
    fd.ng_factor = (fd.nq_factor - 1) * np.tan(1.4 * sl.phi_r)

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)

    kp = (np.tan(np.pi / 4 + sl.phi_r / 2)) ** 2
    # shape factors
    s_c = 1 + 0.2 * kp * fd.width / fd.length
    if sl.phi > 10:
        s_q = 1.0 + 0.1 * kp * fd.width / fd.length
    else:
        s_q = 1.0
    s_g = s_q

    # depth factors
    d_c = 1 + 0.2 * np.sqrt(kp) * fd.depth / fd.width
    if sl.phi > 10:
        d_q = 1 + 0.1 * np.sqrt(kp) * fd.depth / fd.width
    else:
        d_q = 1.0
    d_g = d_q

    # inclination factors:
    theta_load = np.arctan(horizontal_load / vertical_load)
    i_c = (1 - theta_load / (np.pi * 0.5)) ** 2
    i_q = i_c
    if sl.phi > 0:
        i_g = (1 - theta_load / sl.phi_r) ** 2
    else:
        i_g = 0

    # stress at footing base:

    if gwl == 0:
        q_d = sl.unit_bouy_weight * fd.depth
        unit_weight = sl.unit_bouy_weight

    elif gwl > 0 and gwl < fd.depth:
        q_d = (sl.unit_dry_weight * gwl) + (sl.unit_bouy_weight * (fd.depth - gwl))
        unit_weight = sl.unit_bouy_weight

    elif gwl >= fd.depth and gwl <= fd.depth + fd.width:
        sl.average_unit_bouy_weight = sl.unit_bouy_weight + (
        ((gwl - fd.depth) / fd.width) * (sl.unit_dry_weight - sl.unit_bouy_weight))
        q_d = sl.unit_dry_weight * fd.depth
        unit_weight = sl.average_unit_bouy_weight

    elif gwl > fd.depth + fd.width:
        q_d = sl.unit_dry_weight * fd.depth
        unit_weight = sl.unit_dry_weight

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("d_c: ", d_c)
        log("d_q: ", d_q)
        log("d_g: ", d_g)
        log("i_c: ", i_c)
        log("i_q: ", i_q)
        log("i_g: ", i_g)
        log("q_d: ", q_d)

    # Capacity
    fd.q_ult = (sl.cohesion * fd.nc_factor * s_c * d_c * i_c +
                q_d * fd.nq_factor * s_q * d_q * i_q +
                0.5 * fd.width * unit_weight *
                fd.ng_factor * s_g * d_g * i_g)
    return fd.q_ult

def capacity_nzs_vm4_2011(sl, fd, h_l=0, h_b=0, vertical_load=1, slope=0, verbose=0, **kwargs):
    """
    calculates the capacity according to
     Appendix B verification method 4 of the NZ building code

    :param sl: Soil object
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param slope: ground slope
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    # Need to make adjustments if sand  has DR<40% or
    # clay has liquidity indices greater than 0.7

    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    horizontal_load = np.sqrt(h_l ** 2 + h_b ** 2)

    h_eff_b = kwargs.get("h_eff_b", 0)
    h_eff_l = kwargs.get("h_eff_l", 0)
    loc_v_l = kwargs.get("loc_v_l", fd.length / 2)
    loc_v_b = kwargs.get("loc_v_b", fd.width / 2)

    ecc_b = h_b * h_eff_b / vertical_load
    ecc_l = h_l * h_eff_l / vertical_load

    width_eff = min(fd.width, 2 *
                    (loc_v_b + ecc_b), 2 * (fd.width - loc_v_b - ecc_b))
    length_eff = min(fd.length, 2 *
                     (loc_v_l + ecc_l), 2 * (fd.length - loc_v_l - ecc_l))
    area_foundation = length_eff * width_eff

    # check para 3.4.1
    if width_eff / 2 < fd.width / 6:
        raise DesignError("failed on eccentricity")

    # LOAD FACTORS:
    fd.nq_factor = ((np.tan(np.pi / 4 + sl.phi_r / 2)) ** 2 * np.exp(np.pi * np.tan(sl.phi_r)))
    if sl.phi_r == 0:
        fd.nc_factor = 5.14
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)
    fd.ng_factor = 2.0 * (fd.nq_factor - 1) * np.tan(sl.phi_r)

    # shape factors:
    s_c = 1.0 + fd.nq_factor / fd.nc_factor * width_eff / length_eff
    s_q = 1 + width_eff / length_eff * np.tan(sl.phi_r)
    s_g = max(1.0 - 0.4 * width_eff / length_eff, 0.6)  # add limit of 0.6 based on Vesics

    # depth factors:
    if fd.depth / width_eff > 1:
        k = np.arctan(fd.depth / width_eff)
    else:
        k = fd.depth / width_eff
    if sl.phi_r == 0:
        d_c = 1 + 0.4 * k
        d_q = 1.0
    else:
        d_q = (1 + 2 * np.tan(sl.phi_r) *
               (1 - np.sin(sl.phi_r)) ** 2 * k)
        d_c = d_q - (1 - d_q) / (fd.nq_factor * np.tan(sl.phi_r))
    d_g = 1.0

    # load inclination factors:
    if sl.phi_r == 0:
        i_c = 0.5 * (1 + np.sqrt(1 - horizontal_load / (area_foundation * sl.cohesion)))
        i_q = 1.0
        i_g = 1.0
    else:
        if h_b == 0:
            i_q = 1 - horizontal_load / (vertical_load + area_foundation * sl.cohesion /
                                         np.tan(sl.phi_r))
            i_g = i_q
        elif h_b > 0 and h_l == 0:
            i_q = ((1 - 0.7 * horizontal_load / (vertical_load + area_foundation * sl.cohesion /
                                                 np.tan(sl.phi_r))) ** 3)
            i_g = ((1 - horizontal_load / (vertical_load + area_foundation * sl.cohesion /
                                           np.tan(sl.phi_r))) ** 3)
        else:
            raise DesignError("not setup for bi-directional loading")
        i_c = (i_q * fd.nq_factor - 1) / (fd.nq_factor - 1)

    # ground slope factors:
    g_c = 1 - slope * (1.0 - fd.depth / (2 * width_eff)) / 150
    g_q = (1 - np.tan(slope * (1 - fd.depth / (2 * width_eff)))) ** 2
    g_g = g_q

    # stress at footing base:
    q_d = sl.unit_dry_weight * fd.depth

    if verbose:
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("H: ", horizontal_load)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("d_c: ", d_c)
        log("d_q: ", d_q)
        log("d_g: ", d_g)
        log("i_c: ", i_c)
        log("i_q: ", i_q)
        log("i_g: ", i_g)
        log("g_c: ", g_c)
        log("g_q: ", g_q)
        log("g_g: ", g_g)

    # Capacity
    fd.q_ult = (sl.cohesion * fd.nc_factor * s_c * d_c * i_c * g_c +
                q_d * fd.nq_factor * s_q * d_q * i_q * g_q +
                0.5 * width_eff * sl.unit_dry_weight *
                fd.ng_factor * s_g * d_g * i_g * g_g)
    if verbose:
        log("q_ult: ", fd.q_ult)
    return fd.q_ult

def capacity_salgado_2008(sl, fd, h_l=0, h_b=0, vertical_load=1, verbose=0, **kwargs):
    """
    calculates the capacity according to
     THe Engineering of Foundations textbook by Salgado

     ISBN: 0072500581

    :param sl: Soil object
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    # Need to make adjustments if sand  has DR<40% or
    # clay has liquidity indices greater than 0.7
    if not kwargs.get("disable_requires", False):
        models.check_required(sl, ["phi_r", "cohesion", "unit_dry_weight"])
        models.check_required(fd, ["length", "width", "depth"])

    h_eff_b = kwargs.get("h_eff_b", 0)
    h_eff_l = kwargs.get("h_eff_l", 0)
    loc_v_l = kwargs.get("loc_v_l", fd.length / 2)
    loc_v_b = kwargs.get("loc_v_b", fd.width / 2)

    ecc_b = h_b * h_eff_b / vertical_load
    ecc_l = h_l * h_eff_l / vertical_load

    width_eff = min(fd.width, 2 * (loc_v_b + ecc_b), 2 * (fd.width - loc_v_b - ecc_b))
    length_eff = min(fd.length, 2 * (loc_v_l + ecc_l), 2 * (fd.length - loc_v_l - ecc_l))

    # check para 3.4.1
    if width_eff / 2 < fd.width / 6:
        DesignError("failed on eccentricity")

    # LOAD FACTORS:
    fd.nq_factor = np.exp(np.pi * np.tan(sl.phi_r)) * (1 + np.sin(sl.phi_r)) / (1 - np.sin(sl.phi_r))
    fd.ng_factor = 1.5 * (fd.nq_factor - 1) * np.tan(sl.phi_r)
    # fd.ng_factor = (fd.nq_factor - 1) * np.tan(1.32 * sl.phi_r)
    if sl.phi_r == 0:
        fd.nc_factor = 5.14
    else:
        fd.nc_factor = (fd.nq_factor - 1) / np.tan(sl.phi_r)

    # shape factors:
    s_q = 1 + (width_eff / length_eff) * np.tan(sl.phi_r)
    s_g = max(1 - 0.4 * width_eff / length_eff, 0.6)
    s_c = 1.0

    # depth factors:
    d_q = 1 + 2 * np.tan(sl.phi_r) * (1 - np.sin(sl.phi_r)) ** 2 * fd.depth / width_eff
    d_g = 1.0
    d_c = 1.0

    # stress at footing base:
    q_d = sl.unit_dry_weight * fd.depth

    if verbose:
        log("width_eff: ", width_eff)
        log("length_eff: ", length_eff)
        log("Nc: ", fd.nc_factor)
        log("Nq: ", fd.nq_factor)
        log("Ng: ", fd.ng_factor)
        log("s_c: ", s_c)
        log("s_q: ", s_q)
        log("s_g: ", s_g)
        log("d_c: ", d_c)
        log("d_q: ", d_q)
        log("d_g: ", d_g)
        log("q_d: ", q_d)

    # Capacity
    fd.q_ult = (sl.cohesion * fd.nc_factor * s_c * d_c +
                q_d * fd.nq_factor * s_q * d_q +
                0.5 * width_eff * sl.unit_dry_weight *
                fd.ng_factor * s_g * d_g)

    if verbose:
        log("qult: ", fd.q_ult)
    return fd.q_ult

def size_footing_for_capacity(sl, vertical_load, fos=1.0, length_to_width=1.0, verbose=0, **kwargs):
    """
    Determine the size of a footing given an aspect ratio and a load
    :param sl: Soil object
    :param vertical_load: The applied load to the foundation
    :param fos: The target factor of safety
    :param length_to_width: The desired length to width ratio of the foundation
    :param verbose: verbosity
    :return: a Foundation object
    """
    method = kwargs.get("method", 'vesics')
    depth_to_width = kwargs.get("depth_to_width", 0)
    depth = kwargs.get("depth", 0)
    use_depth_to_width = 0
    if not depth:
        use_depth_to_width = 1

    # Find approximate size
    fd = models.FoundationRaft()
    fd.width = .5  # start with B=1.0m
    for i in range(50):
        fd.length = length_to_width * fd.width
        if use_depth_to_width:
            fd.depth = depth_to_width * fd.width
        capacity_method_selector(sl, fd, method)
        q = fd.q_ult

        bearing_capacity = q * fd.length * fd.width
        fs_actual = bearing_capacity / vertical_load

        if fs_actual < fos:
            # Need to increase foundation sizes
            fd.width += 0.5
        else:
            if verbose:
                log("fs_actual: ", fs_actual)
                log("fd.width: ", fd.width)
            break

    # at this stage the current size should be too big
    width_array = []
    fs_array = []
    for j in range(11):
        width_array.append(fd.width)
        fd.length = length_to_width * fd.width
        if use_depth_to_width:
            fd.depth = depth_to_width * fd.width
        capacity_method_selector(sl, fd, method)
        q = fd.q_ult

        capacity = q * fd.length * fd.width

        fs_array.append(capacity / vertical_load)

        fd.width = fd.width - 0.5 / 10

    # search the array until FS satisfied:
    if verbose:
        log("reqFS: ", fos)
        log("width array: \n", width_array)
        log("FS array: \n", fs_array)

    for fs in range(len(fs_array)):
        if fs_array[fs] < fos:
            fd.width = width_array[fs - 1]
            fd.length = length_to_width * fd.width
            if use_depth_to_width:
                fd.depth = depth_to_width * fd.width
            capacity_method_selector(sl, fd, method)
            break
        if fs == len(fs_array) - 1:
            DesignError("No suitable foundation sizes could be determined!")

    return fd

def capacity_method_selector(sl, fd, method, **kwargs):
    """
    Calculates the bearing capacity of a foundation on soil using the specified method.
    :param sl: Soil Object
    :param fd: Foundation Object
    :param method: Method
    :param kwargs:
    :return:
    """

    if method == 'vesics':
        capacity_vesics_1975(sl, fd, **kwargs)
    elif method == 'nzs':
        capacity_nzs_vm4_2011(sl, fd, **kwargs)
    elif method == 'terzaghi':
        capacity_terzaghi_1943(sl, fd, **kwargs)
    elif method == 'hansen':
        capacity_hansen_1970(sl, fd, **kwargs)
    elif method == 'meyerhoff':
        capacity_meyerhof_1963(sl, fd, **kwargs)
    elif method == 'salgado':
        capacity_salgado_2008(sl, fd, **kwargs)

available_methods = {
    "vesics": capacity_vesics_1975,
    "nzs": capacity_nzs_vm4_2011,
    "terzaghi": capacity_terzaghi_1943,
    "hansen": capacity_hansen_1970,
    "meyerhoff": capacity_meyerhof_1963,
    "salgado": capacity_salgado_2008
}

def capacity_from_spt():
    pass
    # Capacity:
    # #Determine the undrained strength (Su) from CPT (Bowles, 1995):
    # Su = (qc - sigma_p_v0)/N_k
    # where N_k ranges from 15-30 depending on
    # consolidation and plastic index.
    # N_k = 13+5.5/50*PI
    # #Determine allowable bearing capacity from SPT (Parry, 1977):
    # q_n_all = 30N_55*(s/25.4) #or check section 4.3.1
    # #or convert to phi
    # phi = 25 + 28 * (N_55 / q)**0.5

def deprecated_capacity_meyerhof_and_hanna_1978(sl_0, sl_1, h0, fd, verbose=0):
    """
    Calculates the two-layered foundation capacity according Meyerhof and Hanna (1978)

    :param sl_0: Top Soil object
    :param sl_1: Base Soil object
    :param h0: Height of top soil layer
    :param fd: Foundation object
    :param h_l: Horizontal load parallel to length
    :param h_b: Horizontal load parallel to width
    :param vertical_load: Vertical load
    :param verbose: verbosity
    :return: ultimate bearing stress
    """

    # UNFINISHED, this code is copied from the Meyerhoff method
    # horizontal_load = np.sqrt(h_l ** 2 + h_b ** 2)

    sl_0.nq_factor_0 = (
        (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_0.phi))))
    if sl_0.phi == 0:
        sl_0.nc_factor_0 = 5.14
    else:
        sl_0.nc_factor_0 = (sl_0.nq_factor_0 - 1) / np.tan(np.deg2rad(sl_0.phi))
    sl_0.ng_factor_0 = (sl_0.nq_factor_0 - 1) * np.tan(1.4 * np.deg2rad(sl_0.phi))

    sl_1.nq_factor_1 = (
        (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_1.phi))))
    if sl_1.phi == 0:
        sl_1.nc_factor_1 = 5.14
    else:
        sl_1.nc_factor_1 = (sl_1.nq_factor_1 - 1) / np.tan(np.deg2rad(sl_1.phi))
    sl_1.ng_factor_1 = (sl_1.nq_factor_1 - 1) * np.tan(1.4 * np.deg2rad(sl_1.phi))

    if verbose:
        log("Nc: ", sl_1.nc_factor_1)
        log("Nq: ", sl_1.nq_factor_1)
        log("Ng: ", sl_1.ng_factor_1)

    sl_0.kp_0 = (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2
    sl_1.kp_1 = (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2
    # shape factors

    # s_c = 1 + 0.2 * kp * fd.width / fd.length
    if sl_0.phi >= 10:
        sl_0.s_c_0 = 1 + 0.2 * sl_0.kp_0 * (fd.width / fd.length)
        sl_0.s_q_0 = 1.0 + 0.1 * sl_0.kp_0 * (fd.width / fd.length)
    else:
        sl_0.s_c_0 = 1 + 0.2 * (fd.width / fd.length)
        sl_0.s_q_0 = 1.0
    sl_0.s_g_0 = sl_0.s_q_0

    if sl_1.phi >= 10:
        sl_1.s_c_1 = 1 + 0.2 * sl_1.kp_1 * (fd.width / fd.length)
        sl_1.s_q_1 = 1.0 + 0.1 * sl_1.kp_1 * (fd.width / fd.length)
    else:
        sl_1.s_c_1 = 1 + 0.2 * (fd.width / fd.length)
        sl_1.s_q_1 = 1.0
    sl_1.s_g_1 = sl_1.s_q_1

    """
    # depth factors
    d_c = 1 + 0.2 * np.sqrt(kp) * fd.depth / fd.width
    if sl_0.phi > 10:
        d_q = 1 + 0.1 * np.sqrt(kp) * fd.depth / fd.width
    else:
        d_q = 1.0
    d_g = d_q

    # inclination factors:
    theta_load = np.arctan(horizontal_load / vertical_load)
    i_c = (1 - theta_load / (np.pi * 0.5)) ** 2
    i_q = i_c
    if sl_0.phi > 0:
        i_g = (1 - theta_load / sl_0.phi_r) ** 2
    else:
        i_g = 0
    """

    # stress at footing base:
    # q_d = sl_0.unit_dry_weight_0 * fd.depth

    # ks
    sl_0.q_0 = (sl_0.cohesion * sl_0.nc_factor_0) + (0.5 * sl_0.unit_dry_weight * fd.width * sl_0.ng_factor_0)
    sl_1.q_1 = (sl_1.cohesion * sl_1.nc_factor_1) + (0.5 * sl_1.unit_dry_weight * fd.width * sl_1.ng_factor_1)
    q1_q0 = sl_1.q_1 / sl_0.q_0

    x_0 = np.array([0, 20.08, 22.42, 25.08, 27.58, 30.08, 32.58, 34.92, 37.83, 40.00, 42.67, 45.00, 47.00, 49.75])
    y_0 = np.array([0.93, 0.93, 0.93, 0.93, 1.01, 1.17, 1.32, 1.56, 1.87, 2.26, 2.72, 3.35, 3.81, 4.82])
    x_2 = np.array([0, 20.08, 22.50, 25.08, 27.58, 30.08, 32.50, 35.00, 37.67, 40.17, 42.67, 45.00, 47.50, 50.00])
    y_2 = np.array([1.55, 1.55, 1.71, 1.86, 2.10, 2.33, 2.72, 3.11, 3.81, 4.43, 5.28, 6.14, 7.46, 9.24])
    x_4 = np.array([0, 20.00, 22.51, 25.10, 27.69, 30.11, 32.45, 35.04, 37.88, 40.14, 42.65, 45.07, 47.33, 50.08])
    y_4 = np.array([2.49, 2.49, 2.64, 2.87, 3.34, 3.81, 4.43, 5.20, 6.29, 7.38, 9.01, 11.11, 14.29, 19.34])
    x_10 = np.array([0, 20.00, 22.50, 25.08, 28.00, 30.00, 32.50, 34.92, 37.50, 40.17, 42.42, 45.00, 47.17, 50.08])
    y_10 = np.array([3.27, 3.27, 3.74, 4.44, 5.37, 6.07, 7.16, 8.33, 10.04, 12.30, 15.95, 21.17, 27.47, 40.00])
    x_int = sl_0.phi

    if sl_0.phi < 1:
        fd.ks = 0
    else:

        if q1_q0 == 0:
            fd.ks = np.interp(x_int, x_0, y_0)

        elif q1_q0 == 0.2:
            fd.ks = np.interp(x_int, x_2, y_2)

        elif q1_q0 == 0.4:
            fd.ks = np.interp(x_int, x_4, y_4)

        elif q1_q0 == 1.0:
            fd.ks = np.interp(x_int, x_10, y_10)

        elif 0 < q1_q0 < 0.2:
            ks_1 = np.interp(x_int, x_0, y_0)
            ks_2 = np.interp(x_int, x_2, y_2)
            fd.ks = (((ks_2 - ks_1) * q1_q0) / 0.2) + ks_1

        elif 0.2 < q1_q0 < 0.4:
            ks_1 = np.interp(x_int, x_2, y_2)
            ks_2 = np.interp(x_int, x_4, y_4)
            fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.2)) / 0.2) + ks_1

        elif 0.4 < q1_q0 < 1.0:
            ks_1 = np.interp(x_int, x_4, y_4)
            ks_2 = np.interp(x_int, x_10, y_10)
            fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.4)) / 0.6) + ks_1
        else:
            raise DesignError("Cannot compute 'ks', bearing ratio out-of-range (q1_q0 = %.3f) required: 0-1." % q1_q0)

    # ca
    if sl_0.cohesion == 0:
        c1_c0 = 0
    else:
        c1_c0 = sl_1.cohesion / sl_0.cohesion
    x = np.array([0.000, 0.082, 0.206, 0.298, 0.404, 0.509, 0.598, 0.685, 0.772])
    y = np.array([0.627, 0.700, 0.794, 0.855, 0.912, 0.948, 0.968, 0.983, 0.997])
    ca_c0 = np.interp(c1_c0, x, y)

    fd.ca = ca_c0 * sl_0.cohesion

    # Capacity
    a = 1  # ????
    s = 1  # ????

    r = 1 + (fd.width / fd.length)
    q_b1 = (sl_1.cohesion * sl_1.nc_factor_1 * sl_1.s_c_1)
    q_b2 = (sl_0.unit_dry_weight * h0 * sl_1.nq_factor_1 * sl_1.s_q_1)
    q_b3 = (sl_1.unit_dry_weight * fd.width * sl_1.ng_factor_1 * sl_1.s_g_1 / 2)
    fd.q_b = q_b1 + q_b2 + q_b3
    fd.q_ult4 = (r * (2 * fd.ca * (h0 - fd.depth) / fd.width) * a)
    fd.q_ult5 = r * (sl_0.unit_dry_weight * ((h0 - fd.depth) ** 2)) * (1 + (2 * fd.depth / (h0 - fd.depth))) * (
        fd.ks * np.tan(np.deg2rad(sl_0.phi)) / fd.width) * s
    fd.q_ult6 = (sl_0.unit_dry_weight * (h0 - fd.depth))
    fd.q_ult = fd.q_b + fd.q_ult4 + fd.q_ult5 - fd.q_ult6

    # maximum value (qu <= qt)
    q_t1 = (sl_0.cohesion * sl_0.nc_factor_0 * sl_0.s_c_0)
    q_t2 = (sl_0.unit_dry_weight * fd.depth * sl_0.nq_factor_0 * sl_0.s_q_0)
    q_t3 = (sl_0.unit_dry_weight * fd.width * sl_0.ng_factor_0 * sl_0.s_g_0 / 2)
    fd.q_t = q_t1 + q_t2 + q_t3

    if fd.q_ult > fd.q_t:
        fd.q_ult = fd.q_t

    return fd.q_ult

def capacity_meyerhof_and_hanna_1978(sl_0, sl_1, h0, fd, gwl=1e6, verbose=0):
    """
    Calculates the two-layered foundation capacity according Meyerhof and Hanna (1978)

    :param sl_0: Top Soil object
    :param sl_1: Base Soil object
    :param h0: Height of top soil layer
    :param fd: Foundation object
    :param wtl: water table level
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    sp = sm.SoilProfile()
    sp.add_layer(0, sl_0)
    sp.add_layer(h0, sl_1)
    sp.gwl = gwl
    return capacity_sp_meyerhof_and_hanna_1978(sp, fd)

    # sl_0.nq_factor_0 = (
    # (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_0.phi))))
    # if sl_0.phi == 0:
    #     sl_0.nc_factor_0 = 5.14
    # else:
    #     sl_0.nc_factor_0 = (sl_0.nq_factor_0 - 1) / np.tan(np.deg2rad(sl_0.phi))
    # sl_0.ng_factor_0 = (sl_0.nq_factor_0 - 1) * np.tan(1.4 * np.deg2rad(sl_0.phi))
    #
    # sl_1.nq_factor_1 = (
    #     (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_1.phi))))
    # if sl_1.phi == 0:
    #     sl_1.nc_factor_1 = 5.14
    # else:
    #     sl_1.nc_factor_1 = (sl_1.nq_factor_1 - 1) / np.tan(np.deg2rad(sl_1.phi))
    # sl_1.ng_factor_1 = (sl_1.nq_factor_1 - 1) * np.tan(1.4 * np.deg2rad(sl_1.phi))
    #
    # if verbose:
    #     log("Nc: ", sl_1.nc_factor_1)
    #     log("Nq: ", sl_1.nq_factor_1)
    #     log("Ng: ", sl_1.ng_factor_1)
    #
    # sl_0.kp_0 = (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2
    # sl_1.kp_1 = (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2
    #
    # # shape factors
    # if sl_0.phi >= 10:
    #     sl_0.s_c_0 = 1 + 0.2 * sl_0.kp_0 * (fd.width / fd.length)
    #     sl_0.s_q_0 = 1.0 + 0.1 * sl_0.kp_0 * (fd.width / fd.length)
    # else:
    #     sl_0.s_c_0 = 1 + 0.2 * (fd.width / fd.length)
    #     sl_0.s_q_0 = 1.0
    # sl_0.s_g_0 = sl_0.s_q_0
    #
    # if sl_1.phi >= 10:
    #     sl_1.s_c_1 = 1 + 0.2 * sl_1.kp_1 * (fd.width / fd.length)
    #     sl_1.s_q_1 = 1.0 + 0.1 * sl_1.kp_1 * (fd.width / fd.length)
    # else:
    #     sl_1.s_c_1 = 1 + 0.2 * (fd.width / fd.length)
    #     sl_1.s_q_1 = 1.0
    # sl_1.s_g_1 = sl_1.s_q_1
    #
    # # Note: this method explicitly accounts for the foundation depth, so there are no depth factors
    # # TODO: inclination factors, see doi.org/10.1139/t78-060
    #
    # # calculate the ca factor
    # if sl_0.cohesion == 0:
    #     c1_c0 = 0
    # else:
    #     c1_c0 = sl_1.cohesion / sl_0.cohesion
    # x = np.array([0.000, 0.082, 0.206, 0.298, 0.404, 0.509, 0.598, 0.685, 0.772])
    # y = np.array([0.627, 0.700, 0.794, 0.855, 0.912, 0.948, 0.968, 0.983, 0.997])
    # ca_c0 = np.interp(c1_c0, x, y)
    # ca = ca_c0 * sl_0.cohesion
    #
    # # Capacity
    # a = 1  # assumed to  be one but can range between 1.1 and 1.27 for square footings according to Das (1999) Ch 4
    # s = 1
    #
    # r = 1 + (fd.width / fd.length)
    #
    # # put the same things before that condition
    # # effective weight not in the soil object
    #
    # if gwl == 0:  # case 1: GWL at surface
    #     q_at_interface = sl_0.unit_bouy_weight * h0
    #     unit_eff_weight_0_at_fd_depth = sl_0.unit_bouy_weight
    #     unit_eff_weight_0_at_interface = sl_0.unit_bouy_weight
    #     unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight
    #
    # elif 0 < gwl <= fd.depth:  # Case 2: GWL at between foundation depth and surface
    #     q_at_interface = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (h0 - gwl))
    #     q_d = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (fd.depth - gwl))
    #     unit_eff_weight_0_at_fd_depth = q_d / fd.depth
    #     unit_eff_weight_0_at_interface = sl_0.unit_bouy_weight
    #     unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight
    #
    # elif fd.depth < gwl <= fd.width + fd.depth:
    #     if gwl < h0:  # Case 3: GWL at between foundation depth and foundation depth plus width, and GWL < layer 1 depth
    #
    #         average_unit_bouy_weight = sl_0.unit_bouy_weight + (
    #         ((gwl - fd.depth) / fd.width) * (sl_0.unit_dry_weight - sl_0.unit_bouy_weight))
    #
    #         q_at_interface = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (h0 - gwl))
    #         unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
    #         unit_eff_weight_0_at_interface = average_unit_bouy_weight
    #         unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight
    #
    #     else:  # Case 4: GWL at between foundation depth and foundation depth plus width, and GWL > layer 1 depth
    #         average_unit_bouy_weight = sl_1.unit_bouy_weight + (
    #         ((gwl - h0) / fd.width) * (sl_1.unit_dry_weight - sl_1.unit_bouy_weight))
    #
    #         q_at_interface = sl_0.unit_dry_weight * h0
    #         unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
    #         unit_eff_weight_0_at_interface = sl_0.unit_dry_weight
    #         unit_eff_weight_1_below_foundation = average_unit_bouy_weight
    #
    # elif gwl > fd.depth + fd.width:  # Case 5: GWL beyond foundation depth plus width
    #     q_at_interface = sl_0.unit_dry_weight * h0
    #     unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
    #     unit_eff_weight_0_at_interface = sl_0.unit_dry_weight
    #     unit_eff_weight_1_below_foundation = sl_1.unit_dry_weight
    # else:
    #     raise ValueError("Could not interpret inputs")  # never reached
    #
    # # maximum value (qu <= qt)
    # q_ult6 = q_at_interface - unit_eff_weight_0_at_fd_depth * fd.depth
    # q_0 = (sl_0.cohesion * sl_0.nc_factor_0) + (0.5 * unit_eff_weight_0_at_interface * fd.width * sl_0.ng_factor_0)
    # q_b2 = (q_at_interface * sl_1.nq_factor_1 * sl_1.s_q_1)
    # q_1 = (sl_1.cohesion * sl_1.nc_factor_1) + (0.5 * unit_eff_weight_1_below_foundation * fd.width * sl_1.ng_factor_1)
    # q_b3 = (unit_eff_weight_1_below_foundation * fd.width * sl_1.ng_factor_1 * sl_1.s_g_1 / 2)
    # q_ult5 = r * (unit_eff_weight_0_at_interface * ((h0 - fd.depth) ** 2)) * (1 + (2 * fd.depth / (h0 - fd.depth))) * (
    #     np.tan(np.deg2rad(sl_0.phi)) / fd.width) * s
    # q_t2 = (unit_eff_weight_0_at_fd_depth * fd.depth * sl_0.nq_factor_0 * sl_0.s_q_0)
    # q_t3 = (unit_eff_weight_0_at_interface * fd.width * sl_0.ng_factor_0 * sl_0.s_g_0 / 2)
    #
    # # qb
    # q_b1 = (sl_1.cohesion * sl_1.nc_factor_1 * sl_1.s_c_1)
    # q_b = q_b1 + q_b2 + q_b3
    #
    # # ks
    # q1_q0 = q_1 / q_0
    #
    # x_0 = np.array([0, 20.08, 22.42, 25.08, 27.58, 30.08, 32.58, 34.92, 37.83, 40.00, 42.67, 45.00, 47.00, 49.75])
    # y_0 = np.array([0.93, 0.93, 0.93, 0.93, 1.01, 1.17, 1.32, 1.56, 1.87, 2.26, 2.72, 3.35, 3.81, 4.82])
    # x_2 = np.array([0, 20.08, 22.50, 25.08, 27.58, 30.08, 32.50, 35.00, 37.67, 40.17, 42.67, 45.00, 47.50, 50.00])
    # y_2 = np.array([1.55, 1.55, 1.71, 1.86, 2.10, 2.33, 2.72, 3.11, 3.81, 4.43, 5.28, 6.14, 7.46, 9.24])
    # x_4 = np.array([0, 20.00, 22.51, 25.10, 27.69, 30.11, 32.45, 35.04, 37.88, 40.14, 42.65, 45.07, 47.33, 50.08])
    # y_4 = np.array([2.49, 2.49, 2.64, 2.87, 3.34, 3.81, 4.43, 5.20, 6.29, 7.38, 9.01, 11.11, 14.29, 19.34])
    # x_10 = np.array([0, 20.00, 22.50, 25.08, 28.00, 30.00, 32.50, 34.92, 37.50, 40.17, 42.42, 45.00, 47.17, 50.08])
    # y_10 = np.array([3.27, 3.27, 3.74, 4.44, 5.37, 6.07, 7.16, 8.33, 10.04, 12.30, 15.95, 21.17, 27.47, 40.00])
    # x_int = sl_0.phi
    #
    # if sl_0.phi < 1:
    #     fd.ks = 0
    # else:
    #
    #     if q1_q0 == 0:
    #         fd.ks = np.interp(x_int, x_0, y_0)
    #
    #     elif q1_q0 == 0.2:
    #         fd.ks = np.interp(x_int, x_2, y_2)
    #
    #     elif q1_q0 == 0.4:
    #         fd.ks = np.interp(x_int, x_4, y_4)
    #
    #     elif q1_q0 == 1.0:
    #         fd.ks = np.interp(x_int, x_10, y_10)
    #
    #     elif 0 < q1_q0 < 0.2:
    #         ks_1 = np.interp(x_int, x_0, y_0)
    #         ks_2 = np.interp(x_int, x_2, y_2)
    #         fd.ks = (((ks_2 - ks_1) * q1_q0) / 0.2) + ks_1
    #
    #     elif 0.2 < q1_q0 < 0.4:
    #         ks_1 = np.interp(x_int, x_2, y_2)
    #         ks_2 = np.interp(x_int, x_4, y_4)
    #         fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.2)) / 0.2) + ks_1
    #
    #     elif 0.4 < q1_q0 < 1.0:
    #         ks_1 = np.interp(x_int, x_4, y_4)
    #         ks_2 = np.interp(x_int, x_10, y_10)
    #         fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.4)) / 0.6) + ks_1
    #     else:
    #         raise DesignError(
    #             "Cannot compute 'ks', bearing ratio out-of-range (q1_q0 = %.3f) required: 0-1." % q1_q0)
    #
    # # qu
    # q_ult4 = (r * (2 * ca * (h0 - fd.depth) / fd.width) * a)
    # q_ult5_ks = q_ult5 * fd.ks
    # q_ult = q_b + q_ult4 + q_ult5_ks - q_ult6
    #
    # q_t1 = (sl_0.cohesion * sl_0.nc_factor_0 * sl_0.s_c_0)
    # q_t = q_t1 + q_t2 + q_t3
    #
    # if q_ult > q_t:
    #     fd.q_ult = q_t
    # else:
    #     fd.q_ult = q_ult
    #
    # return fd.q_ult

def capacity_sp_meyerhof_and_hanna_1978(sp, fd, verbose=0):
    """
    Calculates the two-layered foundation capacity according Meyerhof and Hanna (1978)

    :param sp: Soil profile object
    :param fd: Foundation object
    :param wtl: water table level
    :param verbose: verbosity
    :return: ultimate bearing stress
    """
    assert isinstance(sp, sm.SoilProfile)

    sl_0 = sp.layer(1)
    sl_1 = sp.layer(2)
    h0 = sp.layer_depth(2)
    gwl = sp.gwl

    sl_0.nq_factor_0 = (
    (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_0.phi))))
    if sl_0.phi == 0:
        sl_0.nc_factor_0 = 5.14
    else:
        sl_0.nc_factor_0 = (sl_0.nq_factor_0 - 1) / np.tan(np.deg2rad(sl_0.phi))
    sl_0.ng_factor_0 = (sl_0.nq_factor_0 - 1) * np.tan(1.4 * np.deg2rad(sl_0.phi))

    sl_1.nq_factor_1 = (
        (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2 * np.exp(np.pi * np.tan(np.deg2rad(sl_1.phi))))
    if sl_1.phi == 0:
        sl_1.nc_factor_1 = 5.14
    else:
        sl_1.nc_factor_1 = (sl_1.nq_factor_1 - 1) / np.tan(np.deg2rad(sl_1.phi))
    sl_1.ng_factor_1 = (sl_1.nq_factor_1 - 1) * np.tan(1.4 * np.deg2rad(sl_1.phi))

    if verbose:
        log("Nc: ", sl_1.nc_factor_1)
        log("Nq: ", sl_1.nq_factor_1)
        log("Ng: ", sl_1.ng_factor_1)

    sl_0.kp_0 = (np.tan(np.pi / 4 + np.deg2rad(sl_0.phi / 2))) ** 2
    sl_1.kp_1 = (np.tan(np.pi / 4 + np.deg2rad(sl_1.phi / 2))) ** 2

    # shape factors
    if sl_0.phi >= 10:
        sl_0.s_c_0 = 1 + 0.2 * sl_0.kp_0 * (fd.width / fd.length)
        sl_0.s_q_0 = 1.0 + 0.1 * sl_0.kp_0 * (fd.width / fd.length)
    else:
        sl_0.s_c_0 = 1 + 0.2 * (fd.width / fd.length)
        sl_0.s_q_0 = 1.0
    sl_0.s_g_0 = sl_0.s_q_0

    if sl_1.phi >= 10:
        sl_1.s_c_1 = 1 + 0.2 * sl_1.kp_1 * (fd.width / fd.length)
        sl_1.s_q_1 = 1.0 + 0.1 * sl_1.kp_1 * (fd.width / fd.length)
    else:
        sl_1.s_c_1 = 1 + 0.2 * (fd.width / fd.length)
        sl_1.s_q_1 = 1.0
    sl_1.s_g_1 = sl_1.s_q_1

    # Note: this method explicitly accounts for the foundation depth, so there are no depth factors
    # TODO: inclination factors, see doi.org/10.1139/t78-060

    # Capacity
    a = 1  # assumed to  be one but can range between 1.1 and 1.27 for square footings according to Das (1999) Ch 4
    s = 1

    r = 1 + (fd.width / fd.length)

    # put the same things before that condition
    # effective weight not in the soil object

    if gwl == 0:  # case 1: GWL at surface
        q_at_interface = sl_0.unit_bouy_weight * h0
        unit_eff_weight_0_at_fd_depth = sl_0.unit_bouy_weight
        unit_eff_weight_0_at_interface = sl_0.unit_bouy_weight
        unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight

    elif 0 < gwl <= fd.depth:  # Case 2: GWL at between foundation depth and surface
        q_at_interface = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (h0 - gwl))
        q_d = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (fd.depth - gwl))
        unit_eff_weight_0_at_fd_depth = q_d / fd.depth
        unit_eff_weight_0_at_interface = sl_0.unit_bouy_weight
        unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight

    elif fd.depth < gwl <= fd.width + fd.depth:
        if gwl < h0:  # Case 3: GWL at between foundation depth and foundation depth plus width, and GWL < layer 1 depth

            average_unit_bouy_weight = sl_0.unit_bouy_weight + (
            ((gwl - fd.depth) / fd.width) * (sl_0.unit_dry_weight - sl_0.unit_bouy_weight))

            q_at_interface = (sl_0.unit_dry_weight * gwl) + (sl_0.unit_bouy_weight * (h0 - gwl))
            unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
            unit_eff_weight_0_at_interface = average_unit_bouy_weight
            unit_eff_weight_1_below_foundation = sl_1.unit_bouy_weight

        else:  # Case 4: GWL at between foundation depth and foundation depth plus width, and GWL > layer 1 depth
            average_unit_bouy_weight = sl_1.unit_bouy_weight + (
            ((gwl - h0) / fd.width) * (sl_1.unit_dry_weight - sl_1.unit_bouy_weight))

            q_at_interface = sl_0.unit_dry_weight * h0
            unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
            unit_eff_weight_0_at_interface = sl_0.unit_dry_weight
            unit_eff_weight_1_below_foundation = average_unit_bouy_weight

    elif gwl > fd.depth + fd.width:  # Case 5: GWL beyond foundation depth plus width
        q_at_interface = sl_0.unit_dry_weight * h0
        unit_eff_weight_0_at_fd_depth = sl_0.unit_dry_weight
        unit_eff_weight_0_at_interface = sl_0.unit_dry_weight
        unit_eff_weight_1_below_foundation = sl_1.unit_dry_weight
    else:
        raise ValueError("Could not interpret inputs")  # never reached

    # maximum value (qu <= qt)
    q_ult6 = q_at_interface - unit_eff_weight_0_at_fd_depth * fd.depth
    q_0 = (sl_0.cohesion * sl_0.nc_factor_0) + (0.5 * unit_eff_weight_0_at_interface * fd.width * sl_0.ng_factor_0)
    q_b2 = (q_at_interface * sl_1.nq_factor_1 * sl_1.s_q_1)
    q_1 = (sl_1.cohesion * sl_1.nc_factor_1) + (0.5 * unit_eff_weight_1_below_foundation * fd.width * sl_1.ng_factor_1)
    q_b3 = (unit_eff_weight_1_below_foundation * fd.width * sl_1.ng_factor_1 * sl_1.s_g_1 / 2)
    q_ult5 = r * (unit_eff_weight_0_at_interface * ((h0 - fd.depth) ** 2)) * (1 + (2 * fd.depth / (h0 - fd.depth))) * (
        np.tan(np.deg2rad(sl_0.phi)) / fd.width) * s
    q_t2 = (unit_eff_weight_0_at_fd_depth * fd.depth * sl_0.nq_factor_0 * sl_0.s_q_0)
    q_t3 = (unit_eff_weight_0_at_interface * fd.width * sl_0.ng_factor_0 * sl_0.s_g_0 / 2)

    # qb
    q_b1 = (sl_1.cohesion * sl_1.nc_factor_1 * sl_1.s_c_1)
    q_b = q_b1 + q_b2 + q_b3

    q1_q0 = q_1 / q_0

    # calculate the ca factor
    # if sl_0.cohesion == 0:
    #     c1_c0 = 0
    # else:
    #     c1_c0 = sl_1.cohesion / sl_0.cohesion
    x = np.array([0.000, 0.082, 0.206, 0.298, 0.404, 0.509, 0.598, 0.685, 0.772])
    y = np.array([0.627, 0.700, 0.794, 0.855, 0.912, 0.948, 0.968, 0.983, 0.997])

    # raise Warning("ca should be interpolated using q1/q2 not cohesion, see Figure 4 in MH1978")
    ca_c0 = np.interp(q1_q0, x, y)
    ca = ca_c0 * sl_0.cohesion

    # ks
    x_0 = np.array([0, 20.08, 22.42, 25.08, 27.58, 30.08, 32.58, 34.92, 37.83, 40.00, 42.67, 45.00, 47.00, 49.75])
    y_0 = np.array([0.93, 0.93, 0.93, 0.93, 1.01, 1.17, 1.32, 1.56, 1.87, 2.26, 2.72, 3.35, 3.81, 4.82])
    x_2 = np.array([0, 20.08, 22.50, 25.08, 27.58, 30.08, 32.50, 35.00, 37.67, 40.17, 42.67, 45.00, 47.50, 50.00])
    y_2 = np.array([1.55, 1.55, 1.71, 1.86, 2.10, 2.33, 2.72, 3.11, 3.81, 4.43, 5.28, 6.14, 7.46, 9.24])
    x_4 = np.array([0, 20.00, 22.51, 25.10, 27.69, 30.11, 32.45, 35.04, 37.88, 40.14, 42.65, 45.07, 47.33, 50.08])
    y_4 = np.array([2.49, 2.49, 2.64, 2.87, 3.34, 3.81, 4.43, 5.20, 6.29, 7.38, 9.01, 11.11, 14.29, 19.34])
    x_10 = np.array([0, 20.00, 22.50, 25.08, 28.00, 30.00, 32.50, 34.92, 37.50, 40.17, 42.42, 45.00, 47.17, 50.08])
    y_10 = np.array([3.27, 3.27, 3.74, 4.44, 5.37, 6.07, 7.16, 8.33, 10.04, 12.30, 15.95, 21.17, 27.47, 40.00])
    x_int = sl_0.phi

    if sl_0.phi < 1:
        fd.ks = 0
    else:

        if q1_q0 == 0:
            fd.ks = np.interp(x_int, x_0, y_0)

        elif q1_q0 == 0.2:
            fd.ks = np.interp(x_int, x_2, y_2)

        elif q1_q0 == 0.4:
            fd.ks = np.interp(x_int, x_4, y_4)

        elif q1_q0 == 1.0:
            fd.ks = np.interp(x_int, x_10, y_10)

        elif 0 < q1_q0 < 0.2:
            ks_1 = np.interp(x_int, x_0, y_0)
            ks_2 = np.interp(x_int, x_2, y_2)
            fd.ks = (((ks_2 - ks_1) * q1_q0) / 0.2) + ks_1

        elif 0.2 < q1_q0 < 0.4:
            ks_1 = np.interp(x_int, x_2, y_2)
            ks_2 = np.interp(x_int, x_4, y_4)
            fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.2)) / 0.2) + ks_1

        elif 0.4 < q1_q0 < 1.0:
            ks_1 = np.interp(x_int, x_4, y_4)
            ks_2 = np.interp(x_int, x_10, y_10)
            fd.ks = (((ks_2 - ks_1) * (q1_q0 - 0.4)) / 0.6) + ks_1
        else:
            raise DesignError(
                "Cannot compute 'ks', bearing ratio out-of-range (q1_q0 = %.3f) required: 0-1." % q1_q0)

    # qu
    q_ult4 = (r * (2 * ca * (h0 - fd.depth) / fd.width) * a)
    q_ult5_ks = q_ult5 * fd.ks
    q_ult = q_b + q_ult4 + q_ult5_ks - q_ult6

    q_t1 = (sl_0.cohesion * sl_0.nc_factor_0 * sl_0.s_c_0)
    q_t = q_t1 + q_t2 + q_t3

    if q_ult > q_t:
        if h0 > fd.width/2:
            fd.q_ult = q_t

        else:
            vert_eff_stress_interface = sp.vertical_effective_stress(h0)
            vert_eff_stress_lowest = sp.vertical_effective_stress(fd.width+fd.depth)
            average_eff_stress = (vert_eff_stress_interface + vert_eff_stress_lowest) / 2

            c_2_eff = sl_1.cohesion + average_eff_stress * np.tan(np.radians(sl_1.phi))

            if sl_0.cohesion > c_2_eff:
                fd.q_ult = q_t

            else:
                # vd = {}
                # vd[1] =[1, 1, 1, 1, 1]
                # vd[0.667] = [1, 1.033, 1.064, 1.088, 1.109]
                # vd[0.5] = [1, 1.056, 1.107, 1.152, 1.193]
                # vd[0.333] = [1, 1.088, 1.167, 1.241, 1.311]
                # vd[0.25] = [1, 1.107, 1.208, 1.302, 1.389]
                # vd[0.2] = [1, 1.121, 1.235, 1.342, 1.444]
                # vd[0.1] = [1, 1.154, 1.302, 1.446, 1.584]

                h_over_b = (h0 - fd.depth)/fd.width
                c1_over_c2 =sl_0.cohesion/c_2_eff

                c_1_over_c_2 = [0.1, 0.2, 0.25, 0.333, 0.5, 0.667, 1.]
                m_1 = [1.584, 1.444, 1.389, 1.311, 1.193, 1.109, 1.]
                m_125 = [1.446, 1.342, 1.302, 1.241, 1.152, 1.088, 1.]
                m_167 = [1.302, 1.235, 1.208, 1.167, 1.107, 1.064, 1.]
                m_25 = [1.154, 1.121, 1.107, 1.088, 1.056, 1.033, 1.]
                m_5 = [1, 1, 1, 1, 1, 1, 1]

                if h_over_b == 0.1:
                    m = np.interp(c1_over_c2, c_1_over_c_2, m_1)
                elif h_over_b == 0.125:
                    m = np.interp(c1_over_c2, c_1_over_c_2, m_125)
                elif h_over_b == 0.167:
                    m = np.interp(c1_over_c2, c_1_over_c_2, m_167)
                elif h_over_b == 0.250:
                    m = np.interp(c1_over_c2, c_1_over_c_2, m_25)
                elif h_over_b >= 0.5:
                    m = np.interp(c1_over_c2, c_1_over_c_2, m_5)
                elif 0.1 < h_over_b < 0.125:
                    m_a = np.interp(c1_over_c2, c_1_over_c_2, m_1)
                    m_b = np.interp(c1_over_c2, c_1_over_c_2, m_125)
                    m = np.interp(h_over_b, [0.1,0.125], [m_a,m_b])
                elif 0.125 < h_over_b < 0.167:
                    m_a = np.interp(c1_over_c2, c_1_over_c_2, m_125)
                    m_b = np.interp(c1_over_c2, c_1_over_c_2, m_167)
                    m = np.interp(h_over_b, [0.125, 0.167], [m_a, m_b])
                elif 0.167 < h_over_b < 0.25:
                    m_a = np.interp(c1_over_c2, c_1_over_c_2, m_167)
                    m_b = np.interp(c1_over_c2, c_1_over_c_2, m_25)
                    m = np.interp(h_over_b, [0.167, 0.250], [m_a, m_b])
                elif 0.25 < h_over_b < 0.5:
                    m_a = np.interp(c1_over_c2, c_1_over_c_2, m_25)
                    m_b = np.interp(c1_over_c2, c_1_over_c_2, m_5)
                    m = np.interp(h_over_b, [0.250, 0.500], [m_a, m_b])

                fd.q_ult = (sl_0.cohesion * m * sl_0.nc_factor_0) + (unit_eff_weight_0_at_fd_depth * fd.depth)

    else:
        fd.q_ult = q_ult

    return fd.q_ult


from uw_canvas import Canvas
from uw_canvas.accounts import ACCOUNTS_API
from uw_canvas.models import CanvasRole
from urllib.parse import quote

class Roles(Canvas):
    def get_roles_in_account(self, account_id, params={}):
        """
        List the roles for an account, for the passed Canvas account ID.

        https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.api_index
        """
        url = ACCOUNTS_API.format(account_id) + "/roles"

        roles = []
        for datum in self._get_resource(url, params=params):
            roles.append(CanvasRole(data=datum))
        return roles

    def get_roles_by_account_sis_id(self, account_sis_id, params={}):
        """
        List the roles for an account, for the passed account SIS ID.
        """
        return self.get_roles_in_account(self._sis_id(account_sis_id,
                                                      sis_field="account"),
                                         params)

    def get_effective_course_roles_in_account(self, account_id):
        """
        List all course roles available to an account, for the passed Canvas
        account ID, including course roles inherited from parent accounts.
        """
        course_roles = []
        params = {"show_inherited": "1"}
        for role in self.get_roles_in_account(account_id, params):
            if role.base_role_type != "AccountMembership":
                course_roles.append(role)
        return course_roles

    def get_role(self, account_id, role_id):
        """
        Get information about a single role, for the passed Canvas account ID.

        https://canvas.instructure.com/doc/api/roles.html#method.role_overrides.show
        """
        url = ACCOUNTS_API.format(account_id) + "/roles/{}".format(role_id)
        return CanvasRole(data=self._get_resource(url))

    def get_role_by_account_sis_id(self, account_sis_id, role_id):
        """
        Get information about a single role, for the passed account SIS ID.
        """
        return self.get_role(self._sis_id(account_sis_id, sis_field="account"),
                             role_id)


from uw_canvas import Canvas
from uw_canvas.accounts import ACCOUNTS_API
from uw_canvas.models import CanvasCourse

COURSES_API = "/api/v1/courses/{}"

class Courses(Canvas):
    def get_course(self, course_id, params={}):
        """
        Return course resource for given canvas course id.

        https://canvas.instructure.com/doc/api/courses.html#method.courses.show
        """
        include = params.get("include", [])
        if "term" not in include:
            include.append("term")
        params["include"] = include

        url = COURSES_API.format(course_id)
        return CanvasCourse(data=self._get_resource(url, params=params))

    def get_course_by_sis_id(self, sis_course_id, params={}):
        """
        Return course resource for given sis id.
        """
        return self.get_course(self._sis_id(sis_course_id, sis_field="course"),
                               params)

    def get_courses_in_account(self, account_id, params={}):
        """
        Returns a list of courses for the passed account ID.

        https://canvas.instructure.com/doc/api/accounts.html#method.accounts.courses_api
        """
        if "published" in params:
            params["published"] = "true" if params["published"] else ""

        url = ACCOUNTS_API.format(account_id) + "/courses"

        courses = []
        for data in self._get_paged_resource(url, params=params):
            courses.append(CanvasCourse(data=data))
        return courses

    def get_courses_in_account_by_sis_id(self, sis_account_id, params={}):
        """
        Return a list of courses for the passed account SIS ID.
        """
        return self.get_courses_in_account(
            self._sis_id(sis_account_id, sis_field="account"), params)

    def get_published_courses_in_account(self, account_id, params={}):
        """
        Return a list of published courses for the passed account ID.
        """
        params["published"] = True
        return self.get_courses_in_account(account_id, params)

    def get_published_courses_in_account_by_sis_id(self, sis_account_id,
                                                   params={}):
        """
        Return a list of published courses for the passed account SIS ID.
        """

        return self.get_published_courses_in_account(
            self._sis_id(sis_account_id, sis_field="account"), params)

    def get_courses_for_regid(self, regid, params={}):
        """
        Return a list of courses for the passed regid.

        https://canvas.instructure.com/doc/api/courses.html#method.courses.index
        """
        self._as_user = regid
        data = self._get_resource("/api/v1/courses", params=params)
        self._as_user = None

        courses = []
        for datum in data:
            if "sis_course_id" in datum:
                courses.append(CanvasCourse(data=datum))
            else:
                courses.append(self.get_course(datum["id"], params))

        return courses

    def create_course(self, account_id, course_name):
        """
        Create a canvas course with the given subaccount id and course name.

        https://canvas.instructure.com/doc/api/courses.html#method.courses.create
        """
        url = ACCOUNTS_API.format(account_id) + "/courses"
        body = {"course": {"name": course_name}}
        return CanvasCourse(data=self._post_resource(url, body))

    def update_sis_id(self, course_id, sis_course_id):
        """
        Updates the SIS ID for the course identified by the passed course ID.

        https://canvas.instructure.com/doc/api/courses.html#method.courses.update
        """
        url = COURSES_API.format(course_id)
        body = {"course": {"sis_course_id": sis_course_id}}
        return CanvasCourse(data=self._put_resource(url, body))