Tuesday, July 19, 2016

Derived Django Database Field

The trick to this is creating a custom field and overloading pre_save. Pay special attention to the self.attname member that is set to the value. The source for DateField is a good example. Make sure that if you add any new attributes to the field in it's __init__ method you also add a corresponding deconstruct method.

Monday, July 18, 2016

Mocking Django App

I'm sure this is completely wrong. I needed a Django model for testing, but I don't have a Django app or even a Django project. I'm developing a Django model reader for Carousel, and so I needed a model to test it out with. Sure I could have created a quick django project, but that seemed silly, and my first instinct was to import django.db.models, make a model and use it, but this raised:

ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not
                      configured. You must either define the environment variable
                      DJANGO_SETTINGS_MODULE or call settings.configure() before accessing
                      settings.

Most normal people would turn back now, but instead I imported django.conf.settings and called settings.configure() just like it said to do. Now I got this error:

AppRegistryNotReady: Apps aren't loaded yet.

So now I felt like I was getting somewhere. But where? Googling told me to import django and run setup which I did and that raised:

RuntimeError: Model class __main__.MyModel doesn't declare an explicit app_label and isn't
              in an application in INSTALLED_APPS.

Wow! Normally RuntimeError is a scary warning, like you dumped your core, but this just said I needed to add the app to settings.INSTALLED_APPS, which makes perfect sense, and it also complained that my model wasn't actually part of an app and even explained how to explicitly declare it. Some more Googling and I discovered that app_label is a model option that can be set in class Meta. So I did as told, and it worked!

from django.db import models
from django.conf import settings
import django

MYAPP = 'myapp.MyApp'
settings.configure()
django.setup()
settings.INSTALLED_APPS.append(MYAPP)


class MyModel(models.Model):
    air_temp = models.FloatField()
    latitude = models.FloatField()
    longitude = models.FloatField()
    timezone = models.FloatField()
    pvmodule = models.CharField(max_length=20)

    class Meta:
        app_label = MYAPP


mymodel = MyModel(air_temp=25.0, latitude=38.0, longitude=-122.0,
                  timezone=-8.0, pvmodule='SPR E20-327')

mymodel.__dict__
#{'_state': <django.db.models.base.ModelState at 0x496b2b0>,
# 'air_temp': 25.0,
# 'id': None,
# 'latitude': 38.0,
# 'longitude': -122.0,
# 'pvmodule': 'SPR E20-327',
# 'timezone': -8.0}

Caveats

So I should stop here and point out that that evidently the order of these commands matters, because if I add the fake app to INSTALLED_APPS before calling django.setup() then I get this:

ImportError: No module named myapp

And unfortunately, I just figured this out now, in this post. But this isn't what I originally did. Yes, I'm completely crazy. First I added a fake module called 'myapp' to sys.modules setting it to a mock object, but that didn't work. I got back TypeError: 'Mock' object is not iterable because, as I found out later, there has to be an AppConfig subclass in the app module. But since I didn't know that yet, I did the only logical thing and put the module in a list. What? Yes, did I mention I'm an idiot? This nonsense yielded the following stern warning:

ImproperlyConfigured: The app module [] has no filesystem location, you
                      must configure this app with an AppConfig subclass with a 'path' class
                      attribute.

But this is where I found out about AppConfig in the Django docs which is covered quite nicely. Following the nice directions, I did as told and subclassed AppConfig, added path and also name which I learned from the docs, monkeypatched my mock module with it, and used the dotted name of the app myapp.MyApp now. I felt like I was getting closer, since I only got: AttributeError: __name__ which seemed like a problem with my pretend module. Another monkeypatch and we have my final ludicrously ridiculous hack.

from django.db import models
from django.conf import settings
import django
from django.apps import AppConfig
import sys
import mock

class MyApp(AppConfig):
    """
    Apps subclass ``AppConfig`` and define ``name`` and ``path``
    """
    path = '.'  # path to app
    name = 'myapp'  # name of app


# make a mock module with ``__name__`` and ``MyApp`` member
myapp_module = mock.Mock(__name__='myapp', MyApp=MyApp)
MYAPP = 'myapp.MyApp'  # full path to app
sys.modules['myapp'] = myapp_module  # register module
settings.configure()
settings.INSTALLED_APPS.append(MYAPP)
django.setup()


class MyModel(models.Model):
    air_temp = models.FloatField()
    latitude = models.FloatField()
    longitude = models.FloatField()
    timezone = models.FloatField()
    pvmodule = models.CharField(max_length=20)

    class Meta:
        app_label = MYAPP


mymodel = MyModel(air_temp=25.0, latitude=38.0, longitude=-122.0,
                  timezone=-8.0, pvmodule='SPR E20-327')

mymodel.__dict__
#{'_state': <django.db.models.base.ModelState at 0x496b2b0>,
# 'air_temp': 25.0,
# 'id': None,
# 'latitude': 38.0,
# 'longitude': -122.0,
# 'pvmodule': 'SPR E20-327',
# 'timezone': -8.0}

Yay?

Carousel Python Model Simulation Framework

Carousel - A Python Model Simulation Framework

I want to introduce Carousel, a project that my employer, SunPower has been supporting for use in prediction models. Carousel is an extensible framework for mathematical models that handles generic routines such as loading and saving data, generating reports, converting units, propagating uncertainty and running simulations so developers can focus on creating complex algorithms that are easy to share and maintain.

Introduction

Mathematical models consist of algorithms glued together with generic routines. While the algorithms may sometimes be unique and complex, the rest of the code is often simple and routine. Sometimes mathematical models developed by teams of developers over time become difficult to update because there is no framework for how new data, calculations and outputs are integrated into the existing models. Carousel allows developers to focus on creating complex mathematical models that are robust and easy to maintain by abstracting generic routines and establishing a simple but extensible framework.

The Framework

A Carousel basic model consists of 5 built in layers:

  • Data
  • Formulas
  • Calculations
  • Simulations
  • Outputs

Layers

Carousel is extensible by creating more advanced models and layers. A Carousel model is a collection of layers. Carousel layers share a common base class. Each layer also has a corresponding object and a registry where objects are stored. All layers have a load method that loads all of the layer objects specified into the model. When a model is loaded it loads the objects specified for each layer.

Example

Consider a load shifting algorithm for residential or commercial rooftop solar power. The model might have a performance calculation, a load calculation, a cost calculation and an optimization algorithm that determines how home or business appliances are operated to minimize overall yearly cost of the system.

The performance calculation contains several formulas which require input data from an internal database of solar panel parameters and an online API of weather conditions, so the user creates a data source and reader for each of these. There are some data readers already included in Carousel and once a data reader is created it can be reused in many different projects. Maybe the user submits a pull request to Carousel to add the new API and database reader. The load calculation contains formulas for how the appliances are used. The input data for the appliances are entered into a generic worksheet so the user creates a data source for appliances and uses the XLRDReader to collect the data for each appliance from their worksheets.

The user organizes the formulas into 4 modules, that correspond to each of the calculations, but some formulas are reused since they are generic. For example, data frame summation formulas are used with different time series to create daily, monthly and annual outputs. The user maps out the calculations and specifies their interdependence to other formulas. For example, the cost calculation depends on the load and performance calculations and the optimization algorithm depends on the cost.

The user specifies each output name, initial value and other attributes. Specifications for each layer can be in a JSON parameter file or directly in the code as class attributes; Carousel will interpret either at runtime when it creates the model. Finally the user creates a simulation which in this case is unique because instead of marching through time or space, the simulation iterates over potential load shifting solutions from the algorithm. The user decides which data or outputs to log during the simulation and which to save in reports. Now that the model is created, the user loads the model and sends it the "start" command. After the simulation is complete, the user can examine the outputs and their variances. The outputs will have been automatically converted to the units specified in the model.

Data Layer

The data layer handles all inputs to the models. The data layer object is a data source. Each data source has a data reader. A data source is a document, API, database or other place from which input data for the model can be obtained. The data source and reader provide a framework for specifying how data is acquired. For example a data source for stock market prices might be a public API. An implementation of the stock market API data source specifies the names and attributes of each input data that will be read from the API and how the data reader should read them. The data source is similar to a deserializer because it describes how the data from the source should be interpreted by the model and creates an object in the data registry.

Formula Layer

The formula layer handles operations on input data that generate new outputs. It differs from the calculation layer which handles how formulas are combined together. The formula layer object is a formula, and each formula has a formula importer. For example the Python formula importer can import formulas that are written as Python functions.

Calculation Layer

Calculations are combinations of formulas. Each calculation also has a map of what data and output are used as arguments and what outputs the return values will be. Calculations also implement calculators. Currently there is a static calculator and a dynamic calculator, but new calculators can be implemented that can be reused in other models. The calculation also implements indexing into data and output registries in order retrieve items by index or at a specific time.

Output Layer

Outputs are just like data except they don't need a reader because they are only generated from calculations. Each output is like a serializer because it determines how output objects will be reported or displayed to the user.

Simulation Layer

The simulation layer determines the order of calculations based on the calculation dependencies. It first executes all of the static calculations and then loops over dynamic calculations, displaying logs and saving periodic reports as specified.

Model

The model is a low level class that can be extended to add new layers or implement new simulation commands. Currently only the basic model is implemented. A new model might contain a post processing layer that generates plots and reports from outputs.

Registry

Every layer has a dictionary called a registry that contains all of the layer objects and metadata corresponding to the layer attributes. The registry implements a register method that doesn't allow an item to registered more than once. Each layer registry is subclassed from the base registry so that specific layer attributes can be associated to each key. For example, data sources and outputs have a variance attribute while formulas have an args attribute.

Running Model Simulations

After a model has been described using the framework, it can be loaded. Then any or all of the model simulations can be executed from the model. The simulation specifies commands to the model that user sends using the models command method. Currently the basic model can execute the simulation start command.

Units and Uncertainty

Carousel uses the Pint units wrapper to convert units as specified. Uncertainty is propagated using the UncertaintyWrapper package which was developed for Carousel. It can wrap Python extensions and non-linear algorithms without changing any code. It propagates covariance and sensitivity across all formulas.

Future Work

A basic version of Carousel is ready now. There is an example of a photovoltaic module performance model in the documentation online at GitHub. Some ideas for new features are listed in the Carousel wiki on GitHub.

  • Data validation
  • Reuse 3rd party serializer/deserializer for data layer
  • Model integrity check
  • Database data reader
  • REST API data reader
  • Online repository to share data readers, simualtions, formulas and layers
  • Automatic solver selection
  • Post processing layer
  • Testing tools
  • Concurrency and speedups
  • Remote process and Carousel client

Source, Docs, Issues and Wiki

Previous Presentations

Carousel was presented at the 5th Sandia PVPMC Workshop hosted by EPRI in Santa Clara in May 2016

Acknowledgement

Carousel and UncertaintyWrapper were developed with the support of SunPower Corp. They are distributed with a BSD 3-clause license.

Friday, April 22, 2016

Uncertainty Unwrapped

Please read my disclaimer.

I'm proud to announce UncertaintyWrapper at the Cheese Shop. This work was supported by my employer, SunPower Corp. and is currently offered with a standard 3-clause BSD license. The documentation, source code and releases are also available our SunPower org GitHub page.

So what does uncertainty_wrapper do? Let's say you have a Python function, to calculate solar position, and the function uses a C/C++ library via the Python ctypes library. Or maybe you just have a really complicated set of calculations that you repeat 8760 times, and you want it to run super fast, so you don't want it to calculate derivatives and uncertainty at every internal step, just the final output. Oh and by the way, you want all 8760 calculations vectorized, _ie_: done concurrently as much as possible.

Heres an example using PVLIB of just the first 24 hours.

#
import numpy as np  # v1.11.0
import pandas as pd  # v0.18.0
import pytz  # v2016.1
import pvlib  # v0.3.2
from uncertainty_wrapper import unc_wrapper_args  # v0.4.1

PST = pytz.timezone('US/Pacific')  # Pacific Standard Time
times = pd.DatetimeIndex(start='2015/1/1', end='2015/1/2', freq='1h', tz=PST)  # date range

# arguments for the number of observations
# new in UncertaintyWrapper==0.4.1, jagged arrays are okay
latitude, longitude, pressure, altitude, temperature = 37., -122., 101325., 0., 22.

# standard deviation of 1% assuming normal distribution
covariance = np.tile(np.diag([0.0001] * 5), (times.size, 1, 1))  # tile this for the number of observations


@unc_wrapper_args(1, 2, 3, 4, 5)
# indices specify positions of independent variables:
# 1: latitude, 2: longitude, 3: pressure, 4: altitude, 5: temperature
def spa(times, latitude, longitude, pressure, altitude, temperature):
    # location class only used prior to pvlib-0.3
    dataframe = pvlib.solarposition.spa_c(times, latitude, longitude, pressure, altitude, temperature)
    retvals = dataframe.to_records()
    zenith = retvals['apparent_zenith']
    zenith = np.where(zenith<90, zenith, np.nan)
    azimuth = retvals['azimuth']
    return zenith, azimuth


ze, az, cov, jac = spa(times, latitude, longitude, pressure, altitude, temperature, __covariance__=covariance)
df = pd.DataFrame({'zenith': ze, 'az': az}, index=times)  # easier to view as dataframe
print df
#                                    az     zenith
# 2015-01-01 00:00:00-08:00  349.297715        NaN
# 2015-01-01 01:00:00-08:00   40.210628        NaN
# 2015-01-01 02:00:00-08:00   66.719304        NaN
# 2015-01-01 03:00:00-08:00   80.930185        NaN
# 2015-01-01 04:00:00-08:00   90.852887        NaN
# 2015-01-01 05:00:00-08:00   99.212426        NaN
# 2015-01-01 06:00:00-08:00  107.181217        NaN
# 2015-01-01 07:00:00-08:00  115.450451        NaN
# 2015-01-01 08:00:00-08:00  124.564183  84.113440
# 2015-01-01 09:00:00-08:00  135.023137  74.984664
# 2015-01-01 10:00:00-08:00  147.247403  67.475783
# 2015-01-01 11:00:00-08:00  161.371578  62.273878
# 2015-01-01 12:00:00-08:00  176.922804  60.008978
# 2015-01-01 13:00:00-08:00  192.742327  61.017538
# 2015-01-01 14:00:00-08:00  207.519768  65.144340
# 2015-01-01 15:00:00-08:00  220.494108  71.839001
# 2015-01-01 16:00:00-08:00  231.600910  80.422988
# 2015-01-01 17:00:00-08:00  241.184075  89.948123
# 2015-01-01 18:00:00-08:00  249.726361        NaN
# 2015-01-01 19:00:00-08:00  257.751550        NaN
# 2015-01-01 20:00:00-08:00  265.873170        NaN
# 2015-01-01 21:00:00-08:00  275.014534        NaN
# 2015-01-01 22:00:00-08:00  287.078877        NaN
# 2015-01-01 23:00:00-08:00  307.283646        NaN
# 2015-01-02 00:00:00-08:00  348.921385        NaN

# covariance at 8AM
idx = 8
print times[idx]
# Timestamp('2015-01-01 08:00:00-0800', tz='US/Pacific', offset='H')
nf = 2  # number of dependent variables: [ze, az]
print cov[idx]
# [[ 0.6617299  -0.6152971 ]
#  [-0.6152971   0.62483904]]

# standard deviation
print np.sqrt(cov[8].diagonal())
# [ 0.81346782,  0.79046761]

# Jacobian at 8AM
nargs = 5  # number of independent args
print jac[idx]
# [[  5.56456716e-01  -6.45065654e-01  -1.37538277e-06   0.00000000e+00    4.72409055e-04]
#  [  8.29163154e-02   6.47436098e-01   0.00000000e+00   0.00000000e+00    0.00000000e+00]]
#

First this tells us that the standard deviation of the zenith is 1% if the input has a standard deviation of 1%. That's reasonable. This also tells that zenith is more sensitive to latitude and longitude than pressure or temperature and more sensitive to latitude than azimuth is.

Thursday, November 5, 2015

Wrangling Django ArrayField Migrations

Unfortunately you can't depend on makemigrations to generate the correct SQL to migrate and cast data from a scalar field to a PostgreSQL ARRAY. But Django provides a nifty RunSQL that's also described in this post, "Down and Dirty - 9/25/2013" by Aeracode, the original creator of South predecessor of Django migrations. That post even mentions using RunSQL to alter a column using CAST.

The issue and trick to migrating a column to an ArrayField is given by PostgreSQL in the traceback, which says:

column "my_field " cannot be cast automatically to type double precision[]
HINT:  Specify a USING expression to perform the conversion.
Further hints can be found by rtfm and searching the internet, such this stackoverflow Q&A. My procedure was to use makemigrations to get the state_operations and then wrap each one into a RunSQL migration operation.

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import datetime
from django.utils.timezone import utc
import django.contrib.postgres.fields
import simengapi_app.models
import django.core.validators


class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0XYZ_auto_YYYYMMDD_hhmm'),
    ]

    operations = [
        migrations.RunSQL(
            """
            ALTER TABLE my_app_mymodel
            ALTER COLUMN "my_field"
            TYPE double precision[]
            USING array["my_field"]::double precision[];
            """,
            state_operations=[
                migrations.AlterField(
                    model_name='mymodel',
                    name='my_field',
                    field=django.contrib.postgres.fields.ArrayField(
                        base_field=models.FloatField(), default=list,
                        verbose_name=b'my field', size=None
                    ),
                )
            ],
        ),
    ]

Tuesday, October 20, 2015

REST-ful revelations

I've started using Django REST Framework, and it is simply magic!

Here is a technique I've used to input lists of primitive types and serializers with many=True

from functools import partial
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from my_app.serializers import MyNestedModelSerializer
...

class MyNestedModelViewSet(viewsets.ViewSet):
    serializer_class = MyNestedModelSerializer

    def create(self, request):
        serializer = self.serializer_class(data=request.data)
        # get the submodel list serializer since it can't render/parse html
        submodel_list_serializer = serializer.fields['submodels']
        # make a partial function by setting the submodel list serializer
        partial_get_value = partial(custom_get_value, submodel_list_serializer)
        # monkey patch submodel_list_serializer.get_value() with partial function
        submodel_list_serializer.get_value = partial_get_value
        if serializer.is_valid():
            simulate_data = serializer.save()
            # do stuff ...
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

The function `custom_get_value()` uses JSON to parse the input:

def custom_get_value(serializer, dictionary):
    if serializer.field_name not in dictionary:
        if getattr(serializer.root, 'partial', False):
            return empty
    # We override the default field access in order to support
    # lists in HTML forms.
    if html.is_html_input(dictionary):
        listval = dictionary.getlist(serializer.field_name)
        if len(listval) == 1 and isinstance(listval[0], basestring):
            # get only item in value list, strip leading/trailing whitespace
            listval = listval[0].strip()
            # add brackets if missing so that it's a JSON list
            if not (listval.startswith('[') and listval.endswith(']')):
                listval = '[' + listval + ']'
            # try to deserialize JSON string
            try:
                listval = json.loads(listval)
            except ValueError as err:
                # return original string and log error
                pass
            # set the field with the new value list
            dictionary.setlist(serializer.field_name, listval)
        val = dictionary.getlist(serializer.field_name, [])
        if len(val) > 0:
            # Support QueryDict lists in HTML input.
            return val
        return html.parse_html_list(dictionary, prefix=serializer.field_name)
    return dictionary.get(serializer.field_name, empty)
Fork me on GitHub