<commit_before>"""
Holds files for l1 regularization of LikelihoodModel
"""
import numpy as np
from scipy.optimize import fmin_slsqp
import pdb
# pdb.set_trace

def _fit_l1(f, score, start_params, fargs, kwargs, disp=None, maxiter=100,
        callback=None, retall=False, full_output=False, hess=None):
    """
    Called by base.LikelihoodModel.fit.  Call structure is similar to e.g.
        _fit_mle_newton().

        .. math:: \\ln L=\\sum_{i=1}^{n}\\sum_{j=0}^{J}d_{ij}\\ln\\left(\\frac{\\exp\\left(\\beta_{j}^{\\prime}x_{i}\\right)}{\\sum_{k=0}^{J}\\exp\\left(\\beta_{k}^{\\prime}x_{i}\\right)}\\right)
        :math:`d_{ij}=1`

    The optimization is done by scipy.optimize.fmin_slsqp().  With :math:`L`
    the log-likelihood, we solve the convex but non-smooth problem
    .. math:: \\min_\\beta -\\ln L + \\alpha \\sum_k|\\beta_k|
    via the transformation to the smooth, convex, constrained problem in twice
    as many variables
    .. math:: \\min_{\\beta,u} -\\ln L + \\alpha \\sum_ku_k,
    subject to
    .. math:: -u_k \\leq \\beta_k \\leq u_k.

    Special Parameters
    ------------------
    alpha : Float.  The regularization parameter.
    do_not_regularize_first_param : Boolean.  If the first covariate is a
        constant, then you almost never regularize it.
    """

    if callback:
        print "Callback will be ignored with l1"
    if hess:
        print "Hessian not used with l1, since l1 uses fmin_slsqp"

    ### Extract values
    fargs += (f,score)
    # P is total number of covariates, possibly including a leading constant.
    K = len(start_params)
    fargs += (K,)
    # offset determines which parts of x are used for the dummy variables 'u'
    do_not_regularize_first_param = kwargs.setdefault(
            'do_not_regularize_first_param', False)
    offset = 1 if do_not_regularize_first_param else 0
    fargs += (offset,)
    # The start point
    x0 = np.append(start_params, np.fabs(start_params[offset:]))
    # alpha is the regularization parameter
    alpha = kwargs['alpha']
    fargs += (alpha,)
    # Convert display parameters to scipy.optimize form
    if disp or retall:
        if disp:
            disp_slsqp = 1
        if retall:
            disp_slsqp = 2
    else:
        disp_slsqp = 0
    # Set/retrieve the desired accuracy
    acc = kwargs.setdefault('acc', 1e-6)

    ### Call the optimization
    results = fmin_slsqp(func, x0, f_ieqcons=f_ieqcons, fprime=fprime, acc=acc,
            args=fargs, iter=maxiter, disp=disp_slsqp, full_output=full_output,
            fprime_ieqcons=fprime_ieqcons)

    ### Post-process
    #QA_results(x, params, K, do_not_regularize_first_param, acc)
    if kwargs.get('trim_params'):
        results = trim_params(results, full_output, K, func, fargs, acc, offset)

    ### Pack up return values for statsmodels optimizers
    if full_output:
        x, fx, its, imode, smode = results
        x = np.array(x)
        params = x[:K]
        fopt = fx
        converged = 'True' if imode == 0 else smode
        iterations = its
        # TODO Should gopt be changed to accomidate the regularization term?
        gopt = score(params)
        hopt = hess(params)
        retvals = {'fopt':fopt, 'converged':converged, 'iterations':iterations,
                'gopt':gopt, 'hopt':hopt}
    else:
        x = np.array(results)
        params = x[:K]

    ### Return results
    if full_output:
        return params, retvals
    else:
        return params

def QA_results(x, params, K, do_not_regularize_first_param, acc):
    """
    Raises exception if:
        The dummy variables u are not equal to absolute value of params to within
        min(-log10(10*acc), 10) decimal places
    """
    u = x[K:]
    decimal = min(int(-np.log10(10*acc)), 10)
    offset = 1 if do_not_regularize_first_param else 0
    abs_params = np.fabs(params[offset:])
    try:
        np.testing.assert_array_almost_equal(abs_params, u, decimal=decimal)
    except AssertionError:
        print "abs_params = \n%s\nu = %s"%(abs_params, u)
        raise

def trim_params(results, full_output, K, func, fargs, acc, offset):
    """
    Trims (sets = 0) params that are within max(10*acc, 1e-10) of zero.
    """
    ## Extract params from the results
    trim_tol = min(max(100*acc, 1e-10), 1e-3)
    if full_output:
        x, fx, its, imode, smode = results
    else:
        x = results
    ## Trim the small params
    # If we have a constant column, then don't trim the constant param,
    # since this param was not meant to be regularized.
    for i in xrange(offset, len(x)):
        if abs(x[i]) < trim_tol:
            x[i] = 0.0
    ## Recompute things
    if full_output:
        fx = func(np.array(x), *fargs)
        return x, fx, its, imode, smode
    else:
        return x

def func(x, *fargs):
    """
    The regularized objective function
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]

    return f(params, *args) + alpha * u.sum()

def fprime(x, *fargs):
    """
    The regularized derivative
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]
    # The derivative just appends a vector of constants
    return np.append(score(params, *args), alpha * np.ones(K-offset))

def f_ieqcons(x, *fargs):
    """
    The inequality constraints.
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]
    # All entries in this vector must be \geq 0 in a feasible solution
    return np.append(nonconst_params + u, u - nonconst_params)

def fprime_ieqcons(x, *fargs):
    """
    Derivative of the inequality constraints
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]

    I = np.eye(K-offset)
    A = np.concatenate((I,I), axis=1)
    B = np.concatenate((-I,I), axis=1)
    C = np.concatenate((A,B), axis=0)

    if offset == 0:
        return C
    elif offset == 1:
        one_column = np.zeros((2*K-2, 1))
        return np.concatenate((one_column, C), axis=1)

def nnz_params(results):
    """
    Returns the number of nonzero parameters
    """
    return len(np.nonzero(np.fabs(results.params.ravel())>0)[0])

def modified_df_model(results, model):
    """
    TODO: Not sure if this is done right since I'm not sure what df_model
        is supposed to be.

    In statsmodels, df_model is set first in
        DiscreteModel.initialize() with
            self.df_model = float(tools.rank(self.exog) - 1)
        and then in MultinomialModel.__init__ with
            self.df_model *= (self.J-1)
    """
    number_nonzero_params = nnz_params(results)
    df_model = number_nonzero_params - (model.J - 1)

    return df_model

def modified_bic(results, model):
    """
    Replacement for the "call" results.bic
        (in discrete_model.py:MultinomialResults).  This version uses
        modified_df_model rather than the built in df_model.
    """
    df_model = modified_df_model(results, model)
    return -2*results.llf + np.log(results.nobs)*(df_model+model.J-1)

def modified_aic(results, model):
    """
    Replacement for the "call" results.aic
        (in discrete_model.py:MultinomialResults).  This version uses
        modified_df_model rather than the built in df_model.
    """
    df_model = modified_df_model(results, model)
    return -2*(results.llf - (df_model+model.J-1))

<commit_msg>Modified explanation of the conversion to constrained problem<commit_after>"""
Holds files for l1 regularization of LikelihoodModel
"""
import numpy as np
from scipy.optimize import fmin_slsqp
import pdb
# pdb.set_trace

def _fit_l1(f, score, start_params, fargs, kwargs, disp=None, maxiter=100,
        callback=None, retall=False, full_output=False, hess=None):
    """
    Called by base.LikelihoodModel.fit.  Call structure is similar to e.g.
        _fit_mle_newton().

    The optimization is done by scipy.optimize.fmin_slsqp().  With :math:`L`
    the log-likelihood, we solve the convex but non-smooth problem
    .. math:: \\min_\\beta -\\ln L + \\alpha \\sum_k|\\beta_k|
    via the transformation to the smooth, convex, constrained problem in twice
    as many variables
    .. math:: \\min_{\\beta,u} -\\ln L + \\alpha \\sum_ku_k,
    subject to
    .. math:: -u_k \\leq \\beta_k \\leq u_k.

    Special Parameters
    ------------------
    alpha : Float.  The regularization parameter.
    do_not_regularize_first_param : Boolean.  If the first covariate is a
        constant, then you almost never regularize it.
    """

    if callback:
        print "Callback will be ignored with l1"
    if hess:
        print "Hessian not used with l1, since l1 uses fmin_slsqp"

    ### Extract values
    fargs += (f,score)
    # P is total number of covariates, possibly including a leading constant.
    K = len(start_params)
    fargs += (K,)
    # offset determines which parts of x are used for the dummy variables 'u'
    do_not_regularize_first_param = kwargs.setdefault(
            'do_not_regularize_first_param', False)
    offset = 1 if do_not_regularize_first_param else 0
    fargs += (offset,)
    # The start point
    x0 = np.append(start_params, np.fabs(start_params[offset:]))
    # alpha is the regularization parameter
    alpha = kwargs['alpha']
    fargs += (alpha,)
    # Convert display parameters to scipy.optimize form
    if disp or retall:
        if disp:
            disp_slsqp = 1
        if retall:
            disp_slsqp = 2
    else:
        disp_slsqp = 0
    # Set/retrieve the desired accuracy
    acc = kwargs.setdefault('acc', 1e-6)

    ### Call the optimization
    results = fmin_slsqp(func, x0, f_ieqcons=f_ieqcons, fprime=fprime, acc=acc,
            args=fargs, iter=maxiter, disp=disp_slsqp, full_output=full_output,
            fprime_ieqcons=fprime_ieqcons)

    ### Post-process
    #QA_results(x, params, K, do_not_regularize_first_param, acc)
    if kwargs.get('trim_params'):
        results = trim_params(results, full_output, K, func, fargs, acc, offset)

    ### Pack up return values for statsmodels optimizers
    if full_output:
        x, fx, its, imode, smode = results
        x = np.array(x)
        params = x[:K]
        fopt = fx
        converged = 'True' if imode == 0 else smode
        iterations = its
        # TODO Should gopt be changed to accomidate the regularization term?
        gopt = score(params)
        hopt = hess(params)
        retvals = {'fopt':fopt, 'converged':converged, 'iterations':iterations,
                'gopt':gopt, 'hopt':hopt}
    else:
        x = np.array(results)
        params = x[:K]

    ### Return results
    if full_output:
        return params, retvals
    else:
        return params

def QA_results(x, params, K, do_not_regularize_first_param, acc):
    """
    Raises exception if:
        The dummy variables u are not equal to absolute value of params to within
        min(-log10(10*acc), 10) decimal places
    """
    u = x[K:]
    decimal = min(int(-np.log10(10*acc)), 10)
    offset = 1 if do_not_regularize_first_param else 0
    abs_params = np.fabs(params[offset:])
    try:
        np.testing.assert_array_almost_equal(abs_params, u, decimal=decimal)
    except AssertionError:
        print "abs_params = \n%s\nu = %s"%(abs_params, u)
        raise

def trim_params(results, full_output, K, func, fargs, acc, offset):
    """
    Trims (sets = 0) params that are within max(10*acc, 1e-10) of zero.
    """
    ## Extract params from the results
    trim_tol = min(max(100*acc, 1e-10), 1e-3)
    if full_output:
        x, fx, its, imode, smode = results
    else:
        x = results
    ## Trim the small params
    # If we have a constant column, then don't trim the constant param,
    # since this param was not meant to be regularized.
    for i in xrange(offset, len(x)):
        if abs(x[i]) < trim_tol:
            x[i] = 0.0
    ## Recompute things
    if full_output:
        fx = func(np.array(x), *fargs)
        return x, fx, its, imode, smode
    else:
        return x

def func(x, *fargs):
    """
    The regularized objective function
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]

    return f(params, *args) + alpha * u.sum()

def fprime(x, *fargs):
    """
    The regularized derivative
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]
    # The derivative just appends a vector of constants
    return np.append(score(params, *args), alpha * np.ones(K-offset))

def f_ieqcons(x, *fargs):
    """
    The inequality constraints.
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]
    # All entries in this vector must be \geq 0 in a feasible solution
    return np.append(nonconst_params + u, u - nonconst_params)

def fprime_ieqcons(x, *fargs):
    """
    Derivative of the inequality constraints
    """
    args = fargs[:-5]
    f, score, K, offset, alpha = fargs[-5:]
    params = x[:K]
    nonconst_params = x[offset:K]
    u = x[K:]

    I = np.eye(K-offset)
    A = np.concatenate((I,I), axis=1)
    B = np.concatenate((-I,I), axis=1)
    C = np.concatenate((A,B), axis=0)

    if offset == 0:
        return C
    elif offset == 1:
        one_column = np.zeros((2*K-2, 1))
        return np.concatenate((one_column, C), axis=1)

def nnz_params(results):
    """
    Returns the number of nonzero parameters
    """
    return len(np.nonzero(np.fabs(results.params.ravel())>0)[0])

def modified_df_model(results, model):
    """
    TODO: Not sure if this is done right since I'm not sure what df_model
        is supposed to be.

    In statsmodels, df_model is set first in
        DiscreteModel.initialize() with
            self.df_model = float(tools.rank(self.exog) - 1)
        and then in MultinomialModel.__init__ with
            self.df_model *= (self.J-1)
    """
    number_nonzero_params = nnz_params(results)
    df_model = number_nonzero_params - (model.J - 1)

    return df_model

def modified_bic(results, model):
    """
    Replacement for the "call" results.bic
        (in discrete_model.py:MultinomialResults).  This version uses
        modified_df_model rather than the built in df_model.
    """
    df_model = modified_df_model(results, model)
    return -2*results.llf + np.log(results.nobs)*(df_model+model.J-1)

def modified_aic(results, model):
    """
    Replacement for the "call" results.aic
        (in discrete_model.py:MultinomialResults).  This version uses
        modified_df_model rather than the built in df_model.
    """
    df_model = modified_df_model(results, model)
    return -2*(results.llf - (df_model+model.J-1))

<|endoftext|>


<commit_before>import asyncio
from concurrent.futures import ProcessPoolExecutor
from contextlib import closing
import inspect
import socket
import pytest

def pytest_configure(config):
    config.addinivalue_line("markers",
                            "asyncio: "
                            "mark the test as a coroutine, it will be "
                            "run using an asyncio event loop")
    config.addinivalue_line("markers",
                            "asyncio_process_pool: "
                            "mark the test as a coroutine, it will be "
                            "run using an asyncio event loop with a process "
                            "pool")

@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
    if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
        item = pytest.Function(name, parent=collector)
        if 'asyncio' in item.keywords or 'asyncio_process_pool' in item.keywords:
            return list(collector._genfunctions(name, obj))

def _argnames(func):
    spec = inspect.getfullargspec(func)
    if spec.defaults:
        return spec.args[:-len(spec.defaults)]
    return spec.args

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    """A hook wrapper."""
    if 'asyncio_process_pool' in pyfuncitem.keywords:
        event_loop = pyfuncitem.funcargs.get('event_loop_process_pool')
        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        event_loop.run_until_complete(asyncio.async(pyfuncitem.obj(**funcargs)))
        # prevent other pyfunc calls from executing
        return True
    elif 'asyncio' in pyfuncitem.keywords:
        event_loop = pyfuncitem.funcargs.get('event_loop')
        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        event_loop.run_until_complete(asyncio.async(pyfuncitem.obj(**funcargs)))
        # prevent other pyfunc calls from executing
        return True

def pytest_runtest_setup(item):
    if 'asyncio' in item.keywords and 'event_loop' not in item.fixturenames:
        # inject an event loop fixture for all async tests
        item.fixturenames.append('event_loop')

    if ('asyncio_process_pool' in item.keywords and
                'event_loop_process_pool' not in item.fixturenames):
        item.fixturenames.append('event_loop_process_pool')

@pytest.fixture
def event_loop(request):
    """Create an instance of the default event loop for each test case."""
    policy = asyncio.get_event_loop_policy()

    policy.get_event_loop().close()

    event_loop = policy.new_event_loop()
    policy.set_event_loop(event_loop)

    def _close():
        event_loop.close()

    request.addfinalizer(_close)
    return event_loop

@pytest.fixture
def event_loop_process_pool(event_loop):
    """Create a fresh instance of the default event loop.

    The event loop will have a process pool set as the default executor."""
    event_loop.set_default_executor(ProcessPoolExecutor())
    return event_loop

@pytest.fixture
def unused_tcp_port():
    """Find an unused localhost TCP port from 1024-65535 and return it."""
    with closing(socket.socket()) as sock:
        sock.bind(('127.0.0.1', 0))
        port = sock.getsockname()[1]
    return port<commit_msg>A silly commit for Travis to start paying attention to us.<commit_after>import asyncio
from concurrent.futures import ProcessPoolExecutor
from contextlib import closing
import inspect
import socket
import pytest

def pytest_configure(config):
    config.addinivalue_line("markers",
                            "asyncio: "
                            "mark the test as a coroutine, it will be "
                            "run using an asyncio event loop")
    config.addinivalue_line("markers",
                            "asyncio_process_pool: "
                            "mark the test as a coroutine, it will be "
                            "run using an asyncio event loop with a process "
                            "pool")

@pytest.mark.tryfirst
def pytest_pycollect_makeitem(collector, name, obj):
    if collector.funcnamefilter(name) and inspect.isgeneratorfunction(obj):
        item = pytest.Function(name, parent=collector)
        if ('asyncio' in item.keywords or
           'asyncio_process_pool' in item.keywords):
            return list(collector._genfunctions(name, obj))

def _argnames(func):
    spec = inspect.getfullargspec(func)
    if spec.defaults:
        return spec.args[:-len(spec.defaults)]
    return spec.args

@pytest.mark.tryfirst
def pytest_pyfunc_call(pyfuncitem):
    """A hook wrapper."""
    if 'asyncio_process_pool' in pyfuncitem.keywords:
        event_loop = pyfuncitem.funcargs.get('event_loop_process_pool')
        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        event_loop.run_until_complete(asyncio.async(pyfuncitem.obj(**funcargs)))
        # prevent other pyfunc calls from executing
        return True
    elif 'asyncio' in pyfuncitem.keywords:
        event_loop = pyfuncitem.funcargs.get('event_loop')
        funcargs = dict((arg, pyfuncitem.funcargs[arg])
                        for arg in _argnames(pyfuncitem.obj))
        event_loop.run_until_complete(asyncio.async(pyfuncitem.obj(**funcargs)))
        # prevent other pyfunc calls from executing
        return True

def pytest_runtest_setup(item):
    if 'asyncio' in item.keywords and 'event_loop' not in item.fixturenames:
        # inject an event loop fixture for all async tests
        item.fixturenames.append('event_loop')

    if ('asyncio_process_pool' in item.keywords and
                'event_loop_process_pool' not in item.fixturenames):
        item.fixturenames.append('event_loop_process_pool')

@pytest.fixture
def event_loop(request):
    """Create an instance of the default event loop for each test case."""
    policy = asyncio.get_event_loop_policy()

    policy.get_event_loop().close()

    event_loop = policy.new_event_loop()
    policy.set_event_loop(event_loop)

    def _close():
        event_loop.close()

    request.addfinalizer(_close)
    return event_loop

@pytest.fixture
def event_loop_process_pool(event_loop):
    """Create a fresh instance of the default event loop.

    The event loop will have a process pool set as the default executor."""
    event_loop.set_default_executor(ProcessPoolExecutor())
    return event_loop

@pytest.fixture
def unused_tcp_port():
    """Find an unused localhost TCP port from 1024-65535 and return it."""
    with closing(socket.socket()) as sock:
        sock.bind(('127.0.0.1', 0))
        port = sock.getsockname()[1]
    return port<|endoftext|>


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

from ..io.base import import_matlab_model, load_thermoDB
from cobra.io import load_json_model, load_yaml_model, read_sbml_model
from ..optim.utils import symbol_sum

from ..optim.variables import BinaryVariable
from ..thermo.tmodel import ThermoModel

from numpy import sum

CPLEX = 'optlang-cplex'
GUROBI = 'optlang-gurobi'
GLPK = 'optlang-glpk'

class LumpGEM:
    """
    A class encapsulating the LumpGEM algorithm
    """
    def __init__(self, path_to_model, biomass_rxns, core_subsystems, carbon_uptake, growth_rate,  thermo_data_path):
        """
        : param GEM: the GEM
        : type GEM: cobra model

        : param biomass_rxns: list of biomass reactions
        : type biomass_rxns: [GEM.biomass_rxn.id]

        : param core_subsystems: list of Core subsystems
        : type core_subsytems: [[model.reactions]]

        : param carbon_intake: the amount of carbon atoms the cell intakes from its surrounding
        : type carbon_intake: float

        : param thermo_data_path: the path to the .thermodb database
        : type thermo_data_path : string
        """

        # Load the GEM through the appropriate cobra loading function (based on path suffix)
        model = self._load_model(path_to_model)
        # Build thermo model
        self._tfa_model = self._apply_thermo_constraints(thermo_data_path, model)

        # Extracting all reactions that lead to BBB
        self._rBBB = set([rxn for rxn in self._tfa_model.reactions if rxn.id in biomass_rxns])

        # Set containing every core reaction
        self._rcore = set([])
        # Set containing every core metabolite
        self._mcore = set([])
        for subsystem in core_subsystems:
            for rxn in subsystem:
                # Add rxn to core reactions
                self._rcore.add(rxn)
                # Add involved metabolites to core metabolites
                for met in rxn.metabolites:
                    self._mcore.add(met)

        # Non core reactions
        # TODO improve speed
        self._rncore = set([rxn for rxn in self._tfa_model.reactions if not (rxn in self._rcore or rxn in self._rBBB)])

        # Carbon uptake
        self._C_uptake = carbon_uptake
        # Growth rate
        self._growth_rate = growth_rate

        # TODO : solver choice
        self._solver = 'optlang-cplex'

        self._bin_vars = self._generate_binary_variables()
        self._generate_constraints()
        self._generate_objective()

    def _load_model(self, path_to_model):
        # Matlab
        if path_to_model[-4:] == ".mat":
            return import_matlab_model(path_to_model)

        # YAML
        if path_to_model[-4:] == ".yml":
            return load_yaml_model(path_to_model)

        # JSON
        if path_to_model[-5:] == ".json":
            return load_json_model(path_to_model)

        # SBML
        if path_to_model[-4:] == ".xml":
            return read_sbml_model(path_to_model)

    def _apply_thermo_constraints(self, thermo_data_path, cobra_model):
        """
        Apply thermodynamics constraints defined in thermoDB to Mcore & Rcore
        """
        thermo_data = load_thermoDB(thermo_data_path)
        tfa_model = ThermoModel(thermo_data, cobra_model)
        tfa_model.name = 'Lumped Model'

        # TODO : Check what are these operations for
        # self.read_lexicon = read_lexicon()
        # compartment_data = read_compartment_data()
        # annotate_from_lexicon(tfa_model, lexicon)
        # apply_compartment_data(tfa_model, compartment_data)

        return tfa_model

    def _generate_binary_variables(self):
        """
        :return: A dict associating each non-core reaction with a binary variable
        """
        return {rxn: BinaryVariable(rxn.id, self._tfa_model) for rxn in self._rncore}

    def _generate_constraints(self):
        """
        Generate carbon intake related constraints for each non-core reaction and
        growth rate related constraints for each BBB reaction
        """
        # Carbon intake constraints
        for rxn in self._bin_vars.keys():
            var = self._bin_vars[rxn]
            constraint = self._tfa_model.problem.Constraint(rxn.forward_variable +
                                                            rxn.reverse_variable +
                                                            self._C_uptake * var, ub=self._C_uptake)
            self._tfa_model.add_cons_vars([var, constraint])

    def _generate_objective(self):
        """
        Generate and add the maximization objective
        """
        # Sum of binary variables to be maximized
        objective_sum = symbol_sum(self._bin_vars.values())
        # Set the sum as the objective function
        self._tfa_model.objective = self._tfa_model.problem.Objective(objective_sum, direction='max')

    def run_optimisation(self):
        self._tfa_model.prepare()

        # Deactivate tfa computation for non-core reactions
        for ncrxn in self._rncore:
            ncrxn.thermo['computed'] = False

        self._tfa_model.convert()

        tfa_solution = self._tfa_model.optimize()
        return tfa_solution

    def lump_reaction(self, bio_rxn):
        """
        :param bio_rxn: The objective biomass reaction
        :return:
        """
        # Growth-related constraint
        constraint = self._tfa_model.problem.Constraint(bio_rxn.flux_expression, lb=self._growth_rate)
        self._tfa_model.add_cons_vars(constraint)

        # Computing TFA solution
        solution = self.run_optimisation()

        # Removing the constraint to prevent interference with the next BBB
        self._tfa_model.remove_cons_vars(constraint)

        #active_ncore_reactions = [rxn if self._bin_vars[rxn].Variable.primal != 0 for rxn in self._tfa_model.reactions]
        # TODO symbol_sum here ?
        # TODO use generators to improve speed
        lumped_core_reactions = sum([rxn * solution.fluxes.get(rxn.id) for rxn in self._rcore])
        lumped_ncore_reactions = sum([rxn * solution.fluxes.get(rxn.id)*self._bin_vars[rxn].Variable.primal for rxn in self._rncore])

        lumped_reaction = sum([lumped_core_reactions, lumped_ncore_reactions])

        return lumped_reaction<commit_msg>FIX : corrected wrong use of BinaryVariable<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

from ..io.base import import_matlab_model, load_thermoDB
from cobra.io import load_json_model, load_yaml_model, read_sbml_model
from ..optim.utils import symbol_sum

from ..optim.variables import BinaryVariable
from ..thermo.tmodel import ThermoModel

from numpy import sum

CPLEX = 'optlang-cplex'
GUROBI = 'optlang-gurobi'
GLPK = 'optlang-glpk'

class LumpGEM:
    """
    A class encapsulating the LumpGEM algorithm
    """
    def __init__(self, path_to_model, biomass_rxns, core_subsystems, carbon_uptake, growth_rate,  thermo_data_path):
        """
        : param GEM: the GEM
        : type GEM: cobra model

        : param biomass_rxns: list of biomass reactions
        : type biomass_rxns: [GEM.biomass_rxn.id]

        : param core_subsystems: list of Core subsystems
        : type core_subsytems: [[model.reactions]]

        : param carbon_intake: the amount of carbon atoms the cell intakes from its surrounding
        : type carbon_intake: float

        : param thermo_data_path: the path to the .thermodb database
        : type thermo_data_path : string
        """

        # Load the GEM through the appropriate cobra loading function (based on path suffix)
        model = self._load_model(path_to_model)
        # Build thermo model
        self._tfa_model = self._apply_thermo_constraints(thermo_data_path, model)

        # Extracting all reactions that lead to BBB
        self._rBBB = set([rxn for rxn in self._tfa_model.reactions if rxn.id in biomass_rxns])

        # Set containing every core reaction
        self._rcore = set([])
        # Set containing every core metabolite
        self._mcore = set([])
        for subsystem in core_subsystems:
            for rxn in subsystem:
                # Add rxn to core reactions
                self._rcore.add(rxn)
                # Add involved metabolites to core metabolites
                for met in rxn.metabolites:
                    self._mcore.add(met)

        # Non core reactions
        # TODO improve speed
        self._rncore = set([rxn for rxn in self._tfa_model.reactions if not (rxn in self._rcore or rxn in self._rBBB)])

        # Carbon uptake
        self._C_uptake = carbon_uptake
        # Growth rate
        self._growth_rate = growth_rate

        # TODO : solver choice
        self._solver = 'optlang-cplex'

        self._bin_vars = self._generate_binary_variables()
        self._generate_constraints()
        self._generate_objective()

    def _load_model(self, path_to_model):
        # Matlab
        if path_to_model[-4:] == ".mat":
            return import_matlab_model(path_to_model)

        # YAML
        if path_to_model[-4:] == ".yml":
            return load_yaml_model(path_to_model)

        # JSON
        if path_to_model[-5:] == ".json":
            return load_json_model(path_to_model)

        # SBML
        if path_to_model[-4:] == ".xml":
            return read_sbml_model(path_to_model)

    def _apply_thermo_constraints(self, thermo_data_path, cobra_model):
        """
        Apply thermodynamics constraints defined in thermoDB to Mcore & Rcore
        """
        thermo_data = load_thermoDB(thermo_data_path)
        tfa_model = ThermoModel(thermo_data, cobra_model)
        tfa_model.name = 'Lumped Model'

        # TODO : Check what are these operations for
        # self.read_lexicon = read_lexicon()
        # compartment_data = read_compartment_data()
        # annotate_from_lexicon(tfa_model, lexicon)
        # apply_compartment_data(tfa_model, compartment_data)

        return tfa_model

    def _generate_binary_variables(self):
        """
        :return: A dict associating each non-core reaction with a binary variable
        """
        return {rxn: BinaryVariable(rxn.id, self._tfa_model) for rxn in self._rncore}

    def _generate_constraints(self):
        """
        Generate carbon intake related constraints for each non-core reaction and
        growth rate related constraints for each BBB reaction
        """
        # Carbon intake constraints
        for rxn in self._bin_vars.keys():
            var = self._bin_vars[rxn]
            constraint = self._tfa_model.problem.Constraint(rxn.forward_variable +
                                                            rxn.reverse_variable +
                                                            self._C_uptake * var, ub=self._C_uptake)
            # TODO Checkwhether this is the right way to do it
            self._tfa_model.add_cons_vars([var.variable, constraint])

    def _generate_objective(self):
        """
        Generate and add the maximization objective
        """
        # Sum of binary variables to be maximized
        objective_sum = symbol_sum(self._bin_vars.values())
        # Set the sum as the objective function
        self._tfa_model.objective = self._tfa_model.problem.Objective(objective_sum, direction='max')

    def run_optimisation(self):
        self._tfa_model.prepare()

        # Deactivate tfa computation for non-core reactions
        for ncrxn in self._rncore:
            ncrxn.thermo['computed'] = False

        self._tfa_model.convert()

        tfa_solution = self._tfa_model.optimize()
        return tfa_solution

    def lump_reaction(self, bio_rxn):
        """
        :param bio_rxn: The objective biomass reaction
        :return:
        """
        # Growth-related constraint
        constraint = self._tfa_model.problem.Constraint(bio_rxn.flux_expression, lb=self._growth_rate)
        self._tfa_model.add_cons_vars(constraint)

        # Computing TFA solution
        solution = self.run_optimisation()

        # Removing the constraint to prevent interference with the next BBB
        self._tfa_model.remove_cons_vars(constraint)

        #active_ncore_reactions = [rxn if self._bin_vars[rxn].Variable.primal != 0 for rxn in self._tfa_model.reactions]
        # TODO symbol_sum here ?
        # TODO use generators to improve speed
        lumped_core_reactions = sum([rxn * solution.fluxes.get(rxn.id) for rxn in self._rcore])
        lumped_ncore_reactions = sum([rxn * solution.fluxes.get(rxn.id)*self._bin_vars[rxn].Variable.primal for rxn in self._rncore])

        lumped_reaction = sum([lumped_core_reactions, lumped_ncore_reactions])

        return lumped_reaction
<|endoftext|>


<commit_before>"""
Custom storage for django with Mosso Cloud Files backend.
Created by Rich Leland <rich@richleland.com>.
"""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from django.core.files.storage import Storage
from django.utils.text import get_valid_filename

try:
    import cloudfiles
    from cloudfiles.errors import NoSuchObject
except ImportError:
    raise ImproperlyConfigured("Could not load cloudfiles dependency. See "
                               "http://www.mosso.com/cloudfiles.jsp.")

# TODO: implement TTL into cloudfiles methods
CLOUDFILES_TTL = getattr(settings, 'CLOUDFILES_TTL', 600)

def cloudfiles_upload_to(self, filename):
    """
    Simple, custom upload_to because Cloud Files doesn't support
    nested containers (directories).

    Actually found this out from @minter:
    @richleland The Cloud Files APIs do support pseudo-subdirectories, by
    creating zero-byte files with type application/directory.

    May implement in a future version.
    """
    return get_valid_filename(filename)

class CloudFilesStorage(Storage):
    """
    Custom storage for Mosso Cloud Files.
    """
    default_quick_listdir = True

    def __init__(self, username=None, api_key=None, container=None,
                 connection_kwargs=None):
        """
        Initialize the settings for the connection and container.
        """
        self.username = username or settings.CLOUDFILES_USERNAME
        self.api_key = api_key or settings.CLOUDFILES_API_KEY
        self.container_name = container or settings.CLOUDFILES_CONTAINER
        self.connection_kwargs = connection_kwargs or {}

    def __getstate__(self):
        """
        Return a picklable representation of the storage.
        """
        return dict(username=self.username,
                    api_key=self.api_key,
                    container_name=self.container_name,
                    connection_kwargs=self.connection_kwargs)

    def _get_connection(self):
        if not hasattr(self, '_connection'):
            self._connection = cloudfiles.get_connection(self.username,
                                    self.api_key, **self.connection_kwargs)
        return self._connection

    def _set_connection(self, value):
        self._connection = value

    connection = property(_get_connection, _set_connection)

    def _get_container(self):
        if not hasattr(self, '_container'):
            self.container = self.connection.get_container(
                                                        self.container_name)
        return self._container

    def _set_container(self, container):
        """
        Set the container, making it publicly available (on Limelight CDN) if
        it is not already.
        """
        if not container.is_public():
            container.make_public()
        if hasattr(self, '_container_public_uri'):
            delattr(self, '_container_public_uri')
        self._container = container

    container = property(_get_container, _set_container)

    def _get_container_url(self):
        if not hasattr(self, '_container_public_uri'):
            self._container_public_uri = self.container.public_uri()
        return self._container_public_uri

    container_url = property(_get_container_url)

    def _get_cloud_obj(self, name):
        """
        Helper function to get retrieve the requested Cloud Files Object.
        """
        return self.container.get_object(name)

    def _open(self, name, mode='rb'):
        """
        Return the CloudFilesStorageFile.
        """
        return CloudFilesStorageFile(storage=self, name=name)

    def _save(self, name, content):
        """
        Use the Cloud Files service to write ``content`` to a remote file
        (called ``name``).
        """

        cloud_obj = self.container.create_object(name)
        cloud_obj.size = content.size

        content.open()
        # If the content type is available, pass it in directly rather than
        # getting the cloud object to try to guess.
        if hasattr(content.file, 'content_type'):
            cloud_obj.content_type = content.file.content_type
        cloud_obj.send(content)
        content.close()
        return name

    def delete(self, name):
        """
        Deletes the specified file from the storage system.
        """
        self.container.delete_object(name)

    def exists(self, name):
        """
        Returns True if a file referenced by the given name already exists in
        the storage system, or False if the name is available for a new file.
        """
        try:
            self._get_cloud_obj(name)
            return True
        except NoSuchObject:
            return False

    def listdir(self, path):
        """
        Lists the contents of the specified path, returning a 2-tuple; the
        first being an empty list of directories (not available for quick-
        listing), the second being a list of filenames.

        If the list of directories is required, use the full_listdir method.
        """
        files = []
        if path and not path.endswith('/'):
            path = '%s/' % path
        path_len = len(path)
        for name in self.container.list_objects(path=path):
            files.append(name[path_len:])
        return ([], files)

    def full_listdir(self, path):
        """
        Lists the contents of the specified path, returning a 2-tuple of lists;
        the first item being directories, the second item being files.

        On large containers, this may be a slow operation for root containers
        because every single object must be returned (cloudfiles does not
        provide an explicit way of listing directories).
        """
        dirs = set()
        files = []
        if path and not path.endswith('/'):
            path = '%s/' % path
        path_len = len(path)
        for name in self.container.list_objects(prefix=path):
            name = name[path_len:]
            slash = name[1:-1].find('/') + 1
            if slash:
                dirs.add(name[:slash])
            elif name:
                files.append(name)
        dirs = list(dirs)
        dirs.sort()
        return (dirs, files)

    def size(self, name):
        """
        Returns the total size, in bytes, of the file specified by name.
        """
        return self._get_cloud_obj(name).size

    def url(self, name):
        """
        Returns an absolute URL where the file's contents can be accessed
        directly by a web browser.
        """
        return '%s/%s' % (self.container_url, name)

class CloudFilesStorageFile(File):
    closed = False

    def __init__(self, storage, name, *args, **kwargs):
        self._storage = storage
        super(CloudFilesStorageFile, self).__init__(file=None, name=name,
                                                    *args, **kwargs)

    def _get_size(self):
        if not hasattr(self, '_size'):
            self._size = self._storage.size(self.name)
        return self._size

    def _set_size(self, size):
        self._size = size

    size = property(_get_size, _set_size)

    def _get_file(self):
        if not hasattr(self, '_file'):
            self._file = self._storage._get_cloud_obj(self.name)
        return self._file

    def _set_file(self, value):
        if value is None:
            if hasattr(self, '_file'):
                del self._file
        else:
            self._file = value

    file = property(_get_file, _set_file)

    def read(self, num_bytes=None):
        data = self.file.read(size=num_bytes or -1, offset=self._pos)
        self._pos += len(data)
        return data

    def open(self, *args, **kwargs):
        """
        Open the cloud file object.
        """
        self.file
        self._pos = 0

    def close(self, *args, **kwargs):
        self._pos = 0

    @property
    def closed(self):
        return not hasattr(self, '_file')

    def seek(self, pos):
        self._pos = pos
<commit_msg>Merging bug_70 branch<commit_after>"""
Custom storage for django with Mosso Cloud Files backend.
Created by Rich Leland <rich@richleland.com>.
"""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files import File
from django.core.files.storage import Storage
from django.utils.text import get_valid_filename

try:
    import cloudfiles
    from cloudfiles.errors import NoSuchObject
except ImportError:
    raise ImproperlyConfigured("Could not load cloudfiles dependency. See "
                               "http://www.mosso.com/cloudfiles.jsp.")

# TODO: implement TTL into cloudfiles methods
CLOUDFILES_TTL = getattr(settings, 'CLOUDFILES_TTL', 600)

def cloudfiles_upload_to(self, filename):
    """
    Simple, custom upload_to because Cloud Files doesn't support
    nested containers (directories).

    Actually found this out from @minter:
    @richleland The Cloud Files APIs do support pseudo-subdirectories, by
    creating zero-byte files with type application/directory.

    May implement in a future version.
    """
    return get_valid_filename(filename)

class CloudFilesStorage(Storage):
    """
    Custom storage for Mosso Cloud Files.
    """
    default_quick_listdir = True

    def __init__(self, username=None, api_key=None, container=None,
                 connection_kwargs=None):
        """
        Initialize the settings for the connection and container.
        """
        self.username = username or settings.CLOUDFILES_USERNAME
        self.api_key = api_key or settings.CLOUDFILES_API_KEY
        self.container_name = container or settings.CLOUDFILES_CONTAINER
        self.connection_kwargs = connection_kwargs or {}

    def __getstate__(self):
        """
        Return a picklable representation of the storage.
        """
        return dict(username=self.username,
                    api_key=self.api_key,
                    container_name=self.container_name,
                    connection_kwargs=self.connection_kwargs)

    def _get_connection(self):
        if not hasattr(self, '_connection'):
            self._connection = cloudfiles.get_connection(self.username,
                                    self.api_key, **self.connection_kwargs)
        return self._connection

    def _set_connection(self, value):
        self._connection = value

    connection = property(_get_connection, _set_connection)

    def _get_container(self):
        if not hasattr(self, '_container'):
            self.container = self.connection.get_container(
                                                        self.container_name)
        return self._container

    def _set_container(self, container):
        """
        Set the container, making it publicly available (on Limelight CDN) if
        it is not already.
        """
        if not container.is_public():
            container.make_public()
        if hasattr(self, '_container_public_uri'):
            delattr(self, '_container_public_uri')
        self._container = container

    container = property(_get_container, _set_container)

    def _get_container_url(self):
        if not hasattr(self, '_container_public_uri'):
            self._container_public_uri = self.container.public_uri()
        return self._container_public_uri

    container_url = property(_get_container_url)

    def _get_cloud_obj(self, name):
        """
        Helper function to get retrieve the requested Cloud Files Object.
        """
        return self.container.get_object(name)

    def _open(self, name, mode='rb'):
        """
        Return the CloudFilesStorageFile.
        """
        return CloudFilesStorageFile(storage=self, name=name)

    def _save(self, name, content):
        """
        Use the Cloud Files service to write ``content`` to a remote file
        (called ``name``).
        """

        cloud_obj = self.container.create_object(name)
        cloud_obj.size = content.size

        content.open()
        # If the content type is available, pass it in directly rather than
        # getting the cloud object to try to guess.
        if hasattr(content.file, 'content_type'):
            cloud_obj.content_type = content.file.content_type
        cloud_obj.send(content)
        content.close()
        return name

    def delete(self, name):
        """
        Deletes the specified file from the storage system.
        """
        # If the file exists, delete it.
        if self.exists(name):
            self.container.delete_object(name)

    def exists(self, name):
        """
        Returns True if a file referenced by the given name already exists in
        the storage system, or False if the name is available for a new file.
        """
        try:
            self._get_cloud_obj(name)
            return True
        except NoSuchObject:
            return False

    def listdir(self, path):
        """
        Lists the contents of the specified path, returning a 2-tuple; the
        first being an empty list of directories (not available for quick-
        listing), the second being a list of filenames.

        If the list of directories is required, use the full_listdir method.
        """
        files = []
        if path and not path.endswith('/'):
            path = '%s/' % path
        path_len = len(path)
        for name in self.container.list_objects(path=path):
            files.append(name[path_len:])
        return ([], files)

    def full_listdir(self, path):
        """
        Lists the contents of the specified path, returning a 2-tuple of lists;
        the first item being directories, the second item being files.

        On large containers, this may be a slow operation for root containers
        because every single object must be returned (cloudfiles does not
        provide an explicit way of listing directories).
        """
        dirs = set()
        files = []
        if path and not path.endswith('/'):
            path = '%s/' % path
        path_len = len(path)
        for name in self.container.list_objects(prefix=path):
            name = name[path_len:]
            slash = name[1:-1].find('/') + 1
            if slash:
                dirs.add(name[:slash])
            elif name:
                files.append(name)
        dirs = list(dirs)
        dirs.sort()
        return (dirs, files)

    def size(self, name):
        """
        Returns the total size, in bytes, of the file specified by name.
        """
        return self._get_cloud_obj(name).size

    def url(self, name):
        """
        Returns an absolute URL where the file's contents can be accessed
        directly by a web browser.
        """
        return '%s/%s' % (self.container_url, name)

class CloudFilesStorageFile(File):
    closed = False

    def __init__(self, storage, name, *args, **kwargs):
        self._storage = storage
        super(CloudFilesStorageFile, self).__init__(file=None, name=name,
                                                    *args, **kwargs)

    def _get_size(self):
        if not hasattr(self, '_size'):
            self._size = self._storage.size(self.name)
        return self._size

    def _set_size(self, size):
        self._size = size

    size = property(_get_size, _set_size)

    def _get_file(self):
        if not hasattr(self, '_file'):
            self._file = self._storage._get_cloud_obj(self.name)
        return self._file

    def _set_file(self, value):
        if value is None:
            if hasattr(self, '_file'):
                del self._file
        else:
            self._file = value

    file = property(_get_file, _set_file)

    def read(self, num_bytes=None):
        data = self.file.read(size=num_bytes or -1, offset=self._pos)
        self._pos += len(data)
        return data

    def open(self, *args, **kwargs):
        """
        Open the cloud file object.
        """
        self.file
        self._pos = 0

    def close(self, *args, **kwargs):
        self._pos = 0

    @property
    def closed(self):
        return not hasattr(self, '_file')

    def seek(self, pos):
        self._pos = pos
<|endoftext|>


<commit_before>##########################################################################
#
#  Copyright (c) 2013-2015, Image Engine Design Inc. All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#      * Redistributions of source code must retain the above
#        copyright notice, this list of conditions and the following
#        disclaimer.
#
#      * Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials provided with
#        the distribution.
#
#      * Neither the name of John Haddon nor the names of
#        any other contributors to this software may be used to endorse or
#        promote products derived from this software without specific prior
#        written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
#  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import re
import os
import functools

import IECore

import Gaffer
import GafferUI

Gaffer.Metadata.registerNode(

	Gaffer.Box,

	"description",
	"""
	A container for "subgraphs" - node networks which exist inside the
	Box and can be exposed by promoting selected internal plugs onto the
	outside of the Box.

	Boxes can be used as an organisational tool for simplifying large
	graphs by collapsing them into sections which perform distinct tasks.
	They are also used for authoring files to be used with the Reference
	node.
	""",

	"nodeGraph:childrenViewable", True,

	# Add a + button for creating new plugs in the Settings tab.
	"layout:customWidget:addButton:widgetType", "GafferUI.UserPlugs.plugCreationWidget",
	"layout:customWidget:addButton:section", "Settings",
	"layout:customWidget:addButton:index", -2,

	plugs = {

		"*" : [

			"labelPlugValueWidget:renameable", True,

		],

		"user" : [

			# Disable the + button added by NodeUI, since
			# we want users to use the button in the Settings
			# tab instead.
			"layout:customWidget:addButton:widgetType", "",

		],

	}

)

##########################################################################
# Public functions
##########################################################################

## A command suitable for use with NodeMenu.append(), to add a menu
# item for the creation of a Box from the current selection. We don't
# actually append it automatically, but instead let the startup files
# for particular applications append it if it suits their purposes.
def nodeMenuCreateCommand( menu ) :

	nodeGraph = menu.ancestor( GafferUI.NodeGraph )
	assert( nodeGraph is not None )

	script = nodeGraph.scriptNode()
	graphGadget = nodeGraph.graphGadget()

	return Gaffer.Box.create( graphGadget.getRoot(), script.selection() )

## \deprecated Use NodeGraph.appendSubGraphMenuDefinitions()
def appendNodeContextMenuDefinitions( nodeGraph, node, menuDefinition ) :

	if not isinstance( node, Gaffer.Box ) :
		return

	menuDefinition.append( "/BoxDivider", { "divider" : True } )
	menuDefinition.append( "/Show Contents...", { "command" : IECore.curry( __showContents, nodeGraph, node ) } )

## A callback suitable for use with NodeEditor.toolMenuSignal() - it provides
# menu options specific to Boxes. We don't actually register it automatically,
# but instead let the startup files for particular applications register
# it if it suits their purposes.
def appendNodeEditorToolMenuDefinitions( nodeEditor, node, menuDefinition ) :

	if not isinstance( node, Gaffer.Box ) :
		return

	menuDefinition.append( "/BoxDivider", { "divider" : True } )
	menuDefinition.append( "/Export reference...", { "command" : functools.partial( __exportForReferencing, node = node ) } )
	menuDefinition.append( "/Import reference...", { "command" : functools.partial( __importReference, node = node ) } )

def __showContents( nodeGraph, box ) :

	GafferUI.NodeGraph.acquire( box )

def __exportForReferencing( menu, node ) :

	bookmarks = GafferUI.Bookmarks.acquire( node, category="reference" )

	path = Gaffer.FileSystemPath( bookmarks.getDefault( menu ) )
	path.setFilter( Gaffer.FileSystemPath.createStandardFilter( [ "grf" ] ) )

	dialogue = GafferUI.PathChooserDialogue( path, title="Export reference", confirmLabel="Export", leaf=True, bookmarks=bookmarks )
	path = dialogue.waitForPath( parentWindow = menu.ancestor( GafferUI.Window ) )

	if not path :
		return

	path = str( path )
	if not path.endswith( ".grf" ) :
		path += ".grf"

	node.exportForReference( path )

def __importReference( menu, node ) :

	bookmarks = GafferUI.Bookmarks.acquire( node, category="reference" )

	path = Gaffer.FileSystemPath( bookmarks.getDefault( menu ) )
	path.setFilter( Gaffer.FileSystemPath.createStandardFilter( [ "grf" ] ) )

	window = menu.ancestor( GafferUI.Window )
	dialogue = GafferUI.PathChooserDialogue( path, title="Import reference", confirmLabel="Import", leaf=True, valid=True, bookmarks=bookmarks )
	path = dialogue.waitForPath( parentWindow = window )

	if not path :
		return

	scriptNode = node.ancestor( Gaffer.ScriptNode )
	with GafferUI.ErrorDialogue.ErrorHandler(
		title = "Error Importing Reference",
		closeLabel = "Oy vey",
		parentWindow = window
	) :
		with Gaffer.UndoContext( scriptNode ) :
			scriptNode.executeFile( str( path ), parent = node, continueOnError = True )

# PlugValueWidget registrations
##########################################################################

GafferUI.PlugValueWidget.registerCreator( Gaffer.Box, re.compile( "in[0-9]*" ), None )
GafferUI.PlugValueWidget.registerCreator( Gaffer.Box, re.compile( "out[0-9]*" ), None )

def __correspondingInternalPlug( plug ) :

	node = plug.node()
	if plug.direction() == plug.Direction.In :
		for output in plug.outputs() :
			if node.isAncestorOf( output.node() ) :
				return output
	elif plug.direction() == plug.Direction.Out :
		input = plug.getInput()
		if input is not None and node.isAncestorOf( input.node() ) :
			return input

	return None

def __plugValueWidgetCreator( plug ) :

	# When a plug has been promoted, we get the widget that would
	# have been used to represent the internal plug, and then
	# call setPlug() with the external plug. This allows us to
	# transfer custom uis from inside the node to outside the node.
	#
	# But If the types don't match, we can't expect the
	# UI for the internal plug to work with the external
	# plug. Typically the types will match, because the
	# external plug was created by Box::promotePlug(), but
	# it's possible to use scripting to connect different
	# types, for instance to drive an internal IntPlug with
	# an external BoolPlug. In this case we make no attempt
	# to transfer the internal UI.
	#
	# \todo A better solution may be to mandate the use of Metadata to
	# choose a widget type for each plug, and then just make sure we
	# pass on the internal Metadata.
	internalPlug = __correspondingInternalPlug( plug )
	if type( internalPlug ) is type( plug ) :
		widget = GafferUI.PlugValueWidget.create( internalPlug )
		if widget is not None :
			widget.setPlug( plug )
		return widget

	return GafferUI.PlugValueWidget.create( plug, useTypeOnly=True )

## \todo We're registering the Box PlugValueWidget creator for the Reference node too, because
# we want the two to have the same appearance. We perhaps should just have one registration for
# the SubGraph (the base class for Box and Reference) instead, but it's not yet totally clear
# whether or not we'll have future SubGraph subclasses that will want a different behaviour.
for nodeType in ( Gaffer.Box, Gaffer.Reference ) :

	GafferUI.PlugValueWidget.registerCreator( nodeType, "*" , __plugValueWidgetCreator )

# Shared menu code
##########################################################################

def __deletePlug( plug ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		plug.parent().removeChild( plug )

def __appendPlugDeletionMenuItems( menuDefinition, plug, readOnly = False ) :

	if not isinstance( plug.node(), Gaffer.Box ) :
		return

	menuDefinition.append( "/DeleteDivider", { "divider" : True } )
	menuDefinition.append( "/Delete", {
		"command" : functools.partial( __deletePlug, plug ),
		"active" : not readOnly,
	} )

def __promoteToBox( box, plug ) :

	with Gaffer.UndoContext( box.ancestor( Gaffer.ScriptNode ) ) :
		box.promotePlug( plug )

def __unpromoteFromBox( box, plug ) :

	with Gaffer.UndoContext( box.ancestor( Gaffer.ScriptNode ) ) :
		box.unpromotePlug( plug )

def __promoteToBoxEnabledPlug( box, plug ) :

	with Gaffer.UndoContext( box.ancestor( Gaffer.ScriptNode ) ) :
		enabledPlug = box.getChild( "enabled" )
		if enabledPlug is None :
			enabledPlug = Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
		box["enabled"] = enabledPlug
		plug.setInput( enabledPlug )

def __appendPlugPromotionMenuItems( menuDefinition, plug, readOnly = False ) :

	node = plug.node()
	if node is None :
		return

	box = node.ancestor( Gaffer.Box )
	if box is None :
		return

	if box.canPromotePlug( plug ) :

		if len( menuDefinition.items() ) :
			menuDefinition.append( "/BoxDivider", { "divider" : True } )

		menuDefinition.append( "/Promote to %s" % box.getName(), {
			"command" : IECore.curry( __promoteToBox, box, plug ),
			"active" : not readOnly,
		} )

		if isinstance( plug.parent(), Gaffer.ArrayPlug ) and box.canPromotePlug( plug.parent() ) :
			menuDefinition.append( "/Promote %s array to %s" % ( plug.parent().getName(), box.getName() ), {
				"command" : IECore.curry( __promoteToBox, box, plug.parent() ),
				"active" : not readOnly,
			} )

		if isinstance( node, Gaffer.DependencyNode ) :
			if plug.isSame( node.enabledPlug() ) :
				menuDefinition.append( "/Promote to %s.enabled" % box.getName(), {
					"command" : IECore.curry( __promoteToBoxEnabledPlug, box, plug ),
					"active" : not readOnly,
				} )

	elif box.plugIsPromoted( plug ) :

		# Add a menu item to unpromote the plug

		if len( menuDefinition.items() ) :
			menuDefinition.append( "/BoxDivider", { "divider" : True } )

		if isinstance( plug.parent(), Gaffer.ArrayPlug ) and box.plugIsPromoted( plug.parent() ) :
			menuDefinition.append( "/Unpromote %s array from %s" % ( plug.parent().getName(), box.getName() ), {
				"command" : IECore.curry( __unpromoteFromBox, box, plug.parent() ),
				"active" : not readOnly,
			} )
		else :
			# we dont want to allow unpromoting for children of promoted arrays as it
			# causes unpredicted behaviour, and it doesn't seem useful in general.
			menuDefinition.append( "/Unpromote from %s" % box.getName(), {
				"command" : IECore.curry( __unpromoteFromBox, box, plug ),
				"active" : not readOnly,
			} )

# PlugValueWidget menu
##########################################################################

def __plugPopupMenu( menuDefinition, plugValueWidget ) :

	__appendPlugDeletionMenuItems( menuDefinition, plugValueWidget.getPlug(), readOnly = plugValueWidget.getReadOnly() )
	__appendPlugPromotionMenuItems( menuDefinition, plugValueWidget.getPlug(), readOnly = plugValueWidget.getReadOnly() )

__plugPopupMenuConnection = GafferUI.PlugValueWidget.popupMenuSignal().connect( __plugPopupMenu )

# NodeGraph plug context menu
##########################################################################

def __renamePlug( menu, plug ) :

	d = GafferUI.TextInputDialogue( initialText = plug.getName(), title = "Enter name", confirmLabel = "Rename" )
	name = d.waitForText( parentWindow = menu.ancestor( GafferUI.Window ) )

	if not name :
		return

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		plug.setName( name )

def __setPlugMetadata( plug, key, value ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		Gaffer.Metadata.registerValue( plug, key, value )

def __edgePlugs( nodeGraph, plug ) :

	nodeGadget = nodeGraph.graphGadget().nodeGadget( plug.node() )
	nodule = nodeGadget.nodule( plug )
	return [ n.plug() for n in nodule.parent().children( GafferUI.Nodule ) ]

def __reorderPlugs( plugs, plug, newIndex ) :

	plugs.remove( plug )
	plugs.insert( newIndex, plug )
	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		for index, plug in enumerate( plugs ) :
			Gaffer.Metadata.registerValue( plug, "noduleLayout:index", index )

def __nodeGraphPlugContextMenu( nodeGraph, plug, menuDefinition ) :

	readOnly = Gaffer.MetadataAlgo.readOnly( plug )
	if isinstance( plug.node(), Gaffer.Box ) :

		menuDefinition.append(
			"/Rename...",
			{
				"command" : functools.partial( __renamePlug, plug = plug ),
				"active" : not readOnly,
			}
		)

		menuDefinition.append( "/MoveDivider", { "divider" : True } )

		currentEdge = Gaffer.Metadata.value( plug, "noduleLayout:section" )
		if not currentEdge :
			currentEdge = "top" if plug.direction() == plug.Direction.In else "bottom"

		for edge in ( "top", "bottom", "left", "right" ) :
			menuDefinition.append(
				"/Move To/" + edge.capitalize(),
				{
					"command" : functools.partial( __setPlugMetadata, plug, "noduleLayout:section", edge ),
					"active" : edge != currentEdge and not readOnly,
				}
			)

		edgePlugs = __edgePlugs( nodeGraph, plug )
		edgeIndex = edgePlugs.index( plug )
		menuDefinition.append(
			"/Move " + ( "Up" if currentEdge in ( "left", "right" ) else "Left" ),
			{
				"command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex - 1 ),
				"active" : edgeIndex > 0 and not readOnly,
			}
		)

		menuDefinition.append(
			"/Move " + ( "Down" if currentEdge in ( "left", "right" ) else "Right" ),
			{
				"command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex + 1 ),
				"active" : edgeIndex < len( edgePlugs ) - 1 and not readOnly,
			}
		)

	__appendPlugDeletionMenuItems( menuDefinition, plug, readOnly )
	__appendPlugPromotionMenuItems( menuDefinition, plug, readOnly )

__nodeGraphPlugContextMenuConnection = GafferUI.NodeGraph.plugContextMenuSignal().connect( __nodeGraphPlugContextMenu )
<commit_msg>BoxUI : Prefer PlugAlgo to Box promotion methods<commit_after>##########################################################################
#
#  Copyright (c) 2013-2015, Image Engine Design Inc. All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#      * Redistributions of source code must retain the above
#        copyright notice, this list of conditions and the following
#        disclaimer.
#
#      * Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials provided with
#        the distribution.
#
#      * Neither the name of John Haddon nor the names of
#        any other contributors to this software may be used to endorse or
#        promote products derived from this software without specific prior
#        written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
#  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import re
import os
import functools

import IECore

import Gaffer
import GafferUI

Gaffer.Metadata.registerNode(

	Gaffer.Box,

	"description",
	"""
	A container for "subgraphs" - node networks which exist inside the
	Box and can be exposed by promoting selected internal plugs onto the
	outside of the Box.

	Boxes can be used as an organisational tool for simplifying large
	graphs by collapsing them into sections which perform distinct tasks.
	They are also used for authoring files to be used with the Reference
	node.
	""",

	"nodeGraph:childrenViewable", True,

	# Add a + button for creating new plugs in the Settings tab.
	"layout:customWidget:addButton:widgetType", "GafferUI.UserPlugs.plugCreationWidget",
	"layout:customWidget:addButton:section", "Settings",
	"layout:customWidget:addButton:index", -2,

	plugs = {

		"*" : [

			"labelPlugValueWidget:renameable", True,

		],

		"user" : [

			# Disable the + button added by NodeUI, since
			# we want users to use the button in the Settings
			# tab instead.
			"layout:customWidget:addButton:widgetType", "",

		],

	}

)

##########################################################################
# Public functions
##########################################################################

## A command suitable for use with NodeMenu.append(), to add a menu
# item for the creation of a Box from the current selection. We don't
# actually append it automatically, but instead let the startup files
# for particular applications append it if it suits their purposes.
def nodeMenuCreateCommand( menu ) :

	nodeGraph = menu.ancestor( GafferUI.NodeGraph )
	assert( nodeGraph is not None )

	script = nodeGraph.scriptNode()
	graphGadget = nodeGraph.graphGadget()

	return Gaffer.Box.create( graphGadget.getRoot(), script.selection() )

## \deprecated Use NodeGraph.appendSubGraphMenuDefinitions()
def appendNodeContextMenuDefinitions( nodeGraph, node, menuDefinition ) :

	if not isinstance( node, Gaffer.Box ) :
		return

	menuDefinition.append( "/BoxDivider", { "divider" : True } )
	menuDefinition.append( "/Show Contents...", { "command" : IECore.curry( __showContents, nodeGraph, node ) } )

## A callback suitable for use with NodeEditor.toolMenuSignal() - it provides
# menu options specific to Boxes. We don't actually register it automatically,
# but instead let the startup files for particular applications register
# it if it suits their purposes.
def appendNodeEditorToolMenuDefinitions( nodeEditor, node, menuDefinition ) :

	if not isinstance( node, Gaffer.Box ) :
		return

	menuDefinition.append( "/BoxDivider", { "divider" : True } )
	menuDefinition.append( "/Export reference...", { "command" : functools.partial( __exportForReferencing, node = node ) } )
	menuDefinition.append( "/Import reference...", { "command" : functools.partial( __importReference, node = node ) } )

def __showContents( nodeGraph, box ) :

	GafferUI.NodeGraph.acquire( box )

def __exportForReferencing( menu, node ) :

	bookmarks = GafferUI.Bookmarks.acquire( node, category="reference" )

	path = Gaffer.FileSystemPath( bookmarks.getDefault( menu ) )
	path.setFilter( Gaffer.FileSystemPath.createStandardFilter( [ "grf" ] ) )

	dialogue = GafferUI.PathChooserDialogue( path, title="Export reference", confirmLabel="Export", leaf=True, bookmarks=bookmarks )
	path = dialogue.waitForPath( parentWindow = menu.ancestor( GafferUI.Window ) )

	if not path :
		return

	path = str( path )
	if not path.endswith( ".grf" ) :
		path += ".grf"

	node.exportForReference( path )

def __importReference( menu, node ) :

	bookmarks = GafferUI.Bookmarks.acquire( node, category="reference" )

	path = Gaffer.FileSystemPath( bookmarks.getDefault( menu ) )
	path.setFilter( Gaffer.FileSystemPath.createStandardFilter( [ "grf" ] ) )

	window = menu.ancestor( GafferUI.Window )
	dialogue = GafferUI.PathChooserDialogue( path, title="Import reference", confirmLabel="Import", leaf=True, valid=True, bookmarks=bookmarks )
	path = dialogue.waitForPath( parentWindow = window )

	if not path :
		return

	scriptNode = node.ancestor( Gaffer.ScriptNode )
	with GafferUI.ErrorDialogue.ErrorHandler(
		title = "Error Importing Reference",
		closeLabel = "Oy vey",
		parentWindow = window
	) :
		with Gaffer.UndoContext( scriptNode ) :
			scriptNode.executeFile( str( path ), parent = node, continueOnError = True )

# PlugValueWidget registrations
##########################################################################

GafferUI.PlugValueWidget.registerCreator( Gaffer.Box, re.compile( "in[0-9]*" ), None )
GafferUI.PlugValueWidget.registerCreator( Gaffer.Box, re.compile( "out[0-9]*" ), None )

def __correspondingInternalPlug( plug ) :

	node = plug.node()
	if plug.direction() == plug.Direction.In :
		for output in plug.outputs() :
			if node.isAncestorOf( output.node() ) :
				return output
	elif plug.direction() == plug.Direction.Out :
		input = plug.getInput()
		if input is not None and node.isAncestorOf( input.node() ) :
			return input

	return None

def __plugValueWidgetCreator( plug ) :

	# When a plug has been promoted, we get the widget that would
	# have been used to represent the internal plug, and then
	# call setPlug() with the external plug. This allows us to
	# transfer custom uis from inside the node to outside the node.
	#
	# But If the types don't match, we can't expect the
	# UI for the internal plug to work with the external
	# plug. Typically the types will match, because the
	# external plug was created by PlugAlgo.promote(), but
	# it's possible to use scripting to connect different
	# types, for instance to drive an internal IntPlug with
	# an external BoolPlug. In this case we make no attempt
	# to transfer the internal UI.
	#
	# \todo A better solution may be to mandate the use of Metadata to
	# choose a widget type for each plug, and then just make sure we
	# pass on the internal Metadata.
	internalPlug = __correspondingInternalPlug( plug )
	if type( internalPlug ) is type( plug ) :
		widget = GafferUI.PlugValueWidget.create( internalPlug )
		if widget is not None :
			widget.setPlug( plug )
		return widget

	return GafferUI.PlugValueWidget.create( plug, useTypeOnly=True )

## \todo We're registering the Box PlugValueWidget creator for the Reference node too, because
# we want the two to have the same appearance. We perhaps should just have one registration for
# the SubGraph (the base class for Box and Reference) instead, but it's not yet totally clear
# whether or not we'll have future SubGraph subclasses that will want a different behaviour.
for nodeType in ( Gaffer.Box, Gaffer.Reference ) :

	GafferUI.PlugValueWidget.registerCreator( nodeType, "*" , __plugValueWidgetCreator )

# Shared menu code
##########################################################################

def __deletePlug( plug ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		plug.parent().removeChild( plug )

def __appendPlugDeletionMenuItems( menuDefinition, plug, readOnly = False ) :

	if not isinstance( plug.node(), Gaffer.Box ) :
		return

	menuDefinition.append( "/DeleteDivider", { "divider" : True } )
	menuDefinition.append( "/Delete", {
		"command" : functools.partial( __deletePlug, plug ),
		"active" : not readOnly,
	} )

def __promote( plug ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		Gaffer.PlugAlgo.promote( plug )

def __unpromote( plug ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		Gaffer.PlugAlgo.unpromote( plug )

def __promoteToBoxEnabledPlug( box, plug ) :

	with Gaffer.UndoContext( box.ancestor( Gaffer.ScriptNode ) ) :
		enabledPlug = box.getChild( "enabled" )
		if enabledPlug is None :
			enabledPlug = Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
		box["enabled"] = enabledPlug
		plug.setInput( enabledPlug )

def __appendPlugPromotionMenuItems( menuDefinition, plug, readOnly = False ) :

	node = plug.node()
	if node is None :
		return

	box = node.ancestor( Gaffer.Box )
	if box is None :
		return

	if Gaffer.PlugAlgo.canPromote( plug ) :

		if len( menuDefinition.items() ) :
			menuDefinition.append( "/BoxDivider", { "divider" : True } )

		menuDefinition.append( "/Promote to %s" % box.getName(), {
			"command" : functools.partial( __promote, plug ),
			"active" : not readOnly,
		} )

		if isinstance( plug.parent(), Gaffer.ArrayPlug ) and Gaffer.PlugAlgo.canPromote( plug.parent() ) :
			menuDefinition.append( "/Promote %s array to %s" % ( plug.parent().getName(), box.getName() ), {
				"command" : functools.partial( __promote, plug.parent() ),
				"active" : not readOnly,
			} )

		if isinstance( node, Gaffer.DependencyNode ) :
			if plug.isSame( node.enabledPlug() ) :
				menuDefinition.append( "/Promote to %s.enabled" % box.getName(), {
					"command" : functools.partial( __promoteToBoxEnabledPlug, box, plug ),
					"active" : not readOnly,
				} )

	elif Gaffer.PlugAlgo.isPromoted( plug ) :

		# Add a menu item to unpromote the plug

		if len( menuDefinition.items() ) :
			menuDefinition.append( "/BoxDivider", { "divider" : True } )

		if isinstance( plug.parent(), Gaffer.ArrayPlug ) and Gaffer.PlugAlgo.isPromoted( plug.parent() ) :
			menuDefinition.append( "/Unpromote %s array from %s" % ( plug.parent().getName(), box.getName() ), {
				"command" : functools.partial( __unpromote, plug.parent() ),
				"active" : not readOnly,
			} )
		else :
			# we dont want to allow unpromoting for children of promoted arrays as it
			# causes unpredicted behaviour, and it doesn't seem useful in general.
			menuDefinition.append( "/Unpromote from %s" % box.getName(), {
				"command" : functools.partial( __unpromote, plug ),
				"active" : not readOnly,
			} )

# PlugValueWidget menu
##########################################################################

def __plugPopupMenu( menuDefinition, plugValueWidget ) :

	__appendPlugDeletionMenuItems( menuDefinition, plugValueWidget.getPlug(), readOnly = plugValueWidget.getReadOnly() )
	__appendPlugPromotionMenuItems( menuDefinition, plugValueWidget.getPlug(), readOnly = plugValueWidget.getReadOnly() )

__plugPopupMenuConnection = GafferUI.PlugValueWidget.popupMenuSignal().connect( __plugPopupMenu )

# NodeGraph plug context menu
##########################################################################

def __renamePlug( menu, plug ) :

	d = GafferUI.TextInputDialogue( initialText = plug.getName(), title = "Enter name", confirmLabel = "Rename" )
	name = d.waitForText( parentWindow = menu.ancestor( GafferUI.Window ) )

	if not name :
		return

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		plug.setName( name )

def __setPlugMetadata( plug, key, value ) :

	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		Gaffer.Metadata.registerValue( plug, key, value )

def __edgePlugs( nodeGraph, plug ) :

	nodeGadget = nodeGraph.graphGadget().nodeGadget( plug.node() )
	nodule = nodeGadget.nodule( plug )
	return [ n.plug() for n in nodule.parent().children( GafferUI.Nodule ) ]

def __reorderPlugs( plugs, plug, newIndex ) :

	plugs.remove( plug )
	plugs.insert( newIndex, plug )
	with Gaffer.UndoContext( plug.ancestor( Gaffer.ScriptNode ) ) :
		for index, plug in enumerate( plugs ) :
			Gaffer.Metadata.registerValue( plug, "noduleLayout:index", index )

def __nodeGraphPlugContextMenu( nodeGraph, plug, menuDefinition ) :

	readOnly = Gaffer.MetadataAlgo.readOnly( plug )
	if isinstance( plug.node(), Gaffer.Box ) :

		menuDefinition.append(
			"/Rename...",
			{
				"command" : functools.partial( __renamePlug, plug = plug ),
				"active" : not readOnly,
			}
		)

		menuDefinition.append( "/MoveDivider", { "divider" : True } )

		currentEdge = Gaffer.Metadata.value( plug, "noduleLayout:section" )
		if not currentEdge :
			currentEdge = "top" if plug.direction() == plug.Direction.In else "bottom"

		for edge in ( "top", "bottom", "left", "right" ) :
			menuDefinition.append(
				"/Move To/" + edge.capitalize(),
				{
					"command" : functools.partial( __setPlugMetadata, plug, "noduleLayout:section", edge ),
					"active" : edge != currentEdge and not readOnly,
				}
			)

		edgePlugs = __edgePlugs( nodeGraph, plug )
		edgeIndex = edgePlugs.index( plug )
		menuDefinition.append(
			"/Move " + ( "Up" if currentEdge in ( "left", "right" ) else "Left" ),
			{
				"command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex - 1 ),
				"active" : edgeIndex > 0 and not readOnly,
			}
		)

		menuDefinition.append(
			"/Move " + ( "Down" if currentEdge in ( "left", "right" ) else "Right" ),
			{
				"command" : functools.partial( __reorderPlugs, edgePlugs, plug, edgeIndex + 1 ),
				"active" : edgeIndex < len( edgePlugs ) - 1 and not readOnly,
			}
		)

	__appendPlugDeletionMenuItems( menuDefinition, plug, readOnly )
	__appendPlugPromotionMenuItems( menuDefinition, plug, readOnly )

__nodeGraphPlugContextMenuConnection = GafferUI.NodeGraph.plugContextMenuSignal().connect( __nodeGraphPlugContextMenu )
<|endoftext|>


<commit_before>from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import Auth, BootAuth
from PLC.Messages import Message, Messages
from PLC.Persons import Person, Persons
from PLC.Sites import Site, Sites

class BootNotifyOwners(Method):
    """
    Notify the owners of the node, and/or support about an event that
    happened on the machine.

    Returns 1 if successful.
    """

    roles = ['node']

    accepts = [
        BootAuth(),
        Message.fields['message_id'],
        Parameter(int, "Notify PIs"),
        Parameter(int, "Notify technical contacts"),
        Parameter(int, "Notify support")
        ]

    returns = Parameter(int, '1 if successful')

    def call(self, auth, message_id, include_pis, include_techs, include_support):
        messages = Messages(self.api, [message_id], enabled = True)
        if not messages:
            raise PLCInvalidArgument, "No such message template"

        if not self.api.config.PLC_MAIL_ENABLED:
            return 1

        recipients = {}

        if self.api.config.PLC_MAIL_BOOT_ADDRESS:
            recipients[self.api.config.PLC_MAIL_BOOT_ADDRESS] = "Boot Messages"

        if include_support and self.api.config.PLC_MAIL_SUPPORT_ADDRESS:
            recipients[self.api.config.PLC_MAIL_SUPPORT_ADDRESS] = self.api.config.PLC_NAME + " Support"

        if include_pis or include_techs:
            sites = Sites(self.api, [self.caller['site_id']])
            if not sites:
                raise PLCAPIError, "No site associated with node"
            site = sites[0]

            persons = Persons(self.api, site['person_ids'])
            for person in persons:
                if include_pis and 'pi' in person['roles'] or \
                   include_techs and 'tech' in person['roles']:
                    recipients[person['email']] = person['first_name'] + " " + person['last_name']

        # XXX Send mail

        return 1
<commit_msg>- don't quit if no message template exists<commit_after>from PLC.Faults import *
from PLC.Method import Method
from PLC.Parameter import Parameter, Mixed
from PLC.Auth import Auth, BootAuth
from PLC.Messages import Message, Messages
from PLC.Persons import Person, Persons
from PLC.Sites import Site, Sites

class BootNotifyOwners(Method):
    """
    Notify the owners of the node, and/or support about an event that
    happened on the machine.

    Returns 1 if successful.
    """

    roles = ['node']

    accepts = [
        BootAuth(),
        Message.fields['message_id'],
        Parameter(int, "Notify PIs"),
        Parameter(int, "Notify technical contacts"),
        Parameter(int, "Notify support")
        ]

    returns = Parameter(int, '1 if successful')

    def call(self, auth, message_id, include_pis, include_techs, include_support):
        messages = Messages(self.api, [message_id], enabled = True)
        if not messages:
            # raise PLCInvalidArgument, "No such message template"
            return 1
        message = messages[0]

        if not self.api.config.PLC_MAIL_ENABLED:
            return 1

        recipients = {}

        if self.api.config.PLC_MAIL_BOOT_ADDRESS:
            recipients[self.api.config.PLC_MAIL_BOOT_ADDRESS] = "Boot Messages"

        if include_support and self.api.config.PLC_MAIL_SUPPORT_ADDRESS:
            recipients[self.api.config.PLC_MAIL_SUPPORT_ADDRESS] = self.api.config.PLC_NAME + " Support"

        if include_pis or include_techs:
            sites = Sites(self.api, [self.caller['site_id']])
            if not sites:
                raise PLCAPIError, "No site associated with node"
            site = sites[0]

            persons = Persons(self.api, site['person_ids'])
            for person in persons:
                if include_pis and 'pi' in person['roles'] or \
                   include_techs and 'tech' in person['roles']:
                    recipients[person['email']] = person['first_name'] + " " + person['last_name']

        # XXX Send mail

        return 1
<|endoftext|>


<commit_before>import json
from os.path import expanduser

import pandas
import numpy as np
import matplotlib.pyplot as plt

from aus_map import get_map

filename = (
    'ABS_ANNUAL_ERP_LGA2014_Data_e7f16b2f-edf9-4da4-9cbc-4d2e06154315.csv'
)

def get_data():
    df = pandas.read_csv(filename)

    dat = df[df.Time == 2014]
    return dat[dat['Region Type'] == 'Local Government Areas (2014)']

def get_towns():
    with open(expanduser('~/Dropbox/fuelwatch/data/towns.json')) as fh:
        towns = json.load(fh)

    return {
        name.split(',')[0].strip(): loc
        for name, loc in towns.items()
    }

clean_region = lambda region: ' '.join(region.split()[:-1])

def is_locatable(towns):
    return lambda row: clean_region(row.Region) in towns

def main():
    print('loading towns')
    towns = get_towns()
    print('loading data')
    dat = get_data()

    print('finding towns')
    locatable = dat[dat.apply(is_locatable(towns), 1)]

    print('annotating towns')
    populations = locatable.apply(
        lambda thing: (
            (thing.Value,) +
            tuple(towns[clean_region(thing.Region)])
        ),
        1
    )

    populations = np.array(populations)
    populations = np.array(list(map(np.array, populations)))

    heat = populations[::, 0]
    x = populations[::, 1]
    y = populations[::, 2]

    print('building map')

    colors = np.zeros((len(x), len(y)))
    for sx, sy, sc in zip(range(len(x)), range(len(y)), heat):
        colors[sx][sy] = sc

    get_map()  # bah, globals
    # print('mapping towns')

    # import cartopy.crs as ccrs
    # rotated_pole = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5)

    # plt.pcolor(x, y, colors, cmap='hot_r', transform=rotated_pole)

    print('showing \'em')
    plt.savefig('town_populations.png')

if __name__ == '__main__':
    main()
<commit_msg>Correct import path<commit_after>import json
from os.path import expanduser

import pandas
import numpy as np
import matplotlib.pyplot as plt

from ..aus_map import get_map

filename = (
    'ABS_ANNUAL_ERP_LGA2014_Data_e7f16b2f-edf9-4da4-9cbc-4d2e06154315.csv'
)

def get_data():
    df = pandas.read_csv(filename)

    dat = df[df.Time == 2014]
    return dat[dat['Region Type'] == 'Local Government Areas (2014)']

def get_towns():
    with open(expanduser('~/Dropbox/fuelwatch/data/towns.json')) as fh:
        towns = json.load(fh)

    return {
        name.split(',')[0].strip(): loc
        for name, loc in towns.items()
    }

clean_region = lambda region: ' '.join(region.split()[:-1])

def is_locatable(towns):
    return lambda row: clean_region(row.Region) in towns

def main():
    print('loading towns')
    towns = get_towns()
    print('loading data')
    dat = get_data()

    print('finding towns')
    locatable = dat[dat.apply(is_locatable(towns), 1)]

    print('annotating towns')
    populations = locatable.apply(
        lambda thing: (
            (thing.Value,) +
            tuple(towns[clean_region(thing.Region)])
        ),
        1
    )

    populations = np.array(populations)
    populations = np.array(list(map(np.array, populations)))

    heat = populations[::, 0]
    x = populations[::, 1]
    y = populations[::, 2]

    print('building map')

    colors = np.zeros((len(x), len(y)))
    for sx, sy, sc in zip(range(len(x)), range(len(y)), heat):
        colors[sx][sy] = sc

    get_map()  # bah, globals
    # print('mapping towns')

    # import cartopy.crs as ccrs
    # rotated_pole = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5)

    # plt.pcolor(x, y, colors, cmap='hot_r', transform=rotated_pole)

    print('showing \'em')
    plt.savefig('town_populations.png')

if __name__ == '__main__':
    main()
<|endoftext|>