Friendly Sam is a software toolbox for optimization-based modeling and simulation

Friendly Sam is a toolbox developed to formulate and solve optimization-based models of energy systems, but it could be used for many other systems too. Friendly Sam is designed to produce readable and understandable model specifications. It is developed with the Python ecosystem of scientific tools in mind and can be used together with numpy, pandas, matplotlib and many of your other favorite tools.

Note

Friendly Sam is work in progress. Please post any questions or issues on the Issue Tracker.

Friendly Sam is friendly in a number of ways:

Flows of resources
The frien in friendly stands for flows of resources in energy system networks. With Friendly Sam, we model power plants, energy storages, consumers and other components as nodes in a network, interconnected by flows of “resources”. Resources is a common name for all the different flows you could model: district heating and cooling, electric power, fuels, etc.
User-friendly
Friendly Sam is user-friendly. Instead of a global namespace with variable names like VHSTOLOADT, we use object-oriented code with descriptive names like model[“Heat storage A”].accumulation(42). Your model becomes easier to write, understand and maintain.
Open source
Friendly Sam is open source software, because we think it’s friendly and smart to collaborate. Friendly Sam is released under LGPL v3 license. The source code is on GitHub.

Contents:

How to install Friendly Sam

Get Python 3

Friendly Sam is developed in Python 3 (at the time of this writing, Python 3.4). Download and install it now, if you haven’t already.

Use a virtual environment

It is highly recommended that you use a virtual environment. It’s not strictly necessary, but if you choose not to, there is a risk that you will have conflicts between different versions of the packages that Friendly Sam and other Python packages depend on. Google for python virtualenv if you want to learn more. If not, you can also do it the way I do, using vex.

  • If you are on Windows
    1. Open a command prompt.
    2. Make sure you have the latest setuptools by running pip install setuptools --upgrade
    3. Install vex by running pip install --user vex
    4. Create a virtual environment named my_project_name and enter it by running vex -m --python C:\Python34\python.exe my_project_name cmd

    Now, whenever you want to use your virtual environment, open a command prompt and run vex my_project_name cmd.

  • If you are on Linux

    Basically, you follow the instructions for Windows above but exchange C:\Python34\python.exe for something more suitable, and then do vex my_project_name bash instead. Also see the docs for vex if you have problems.

Install Friendly Sam

Assuming you have entered/activated your Python virtual environment, or wherever you want to install it, open a command prompt/shell and run the command:

pip install friendlysam

Optional dependencies

If you want to add support for pandas related stuff, or for saving and loading models using dill, do one of:

pip install friendlysam[pandas]
pip install friendlysam[pickling]
pip install friendlysam[pandas,pickling]

For developers

Install in developer mode

If you are developing the source code of Friendly Sam, you probably want to install it in “develop” mode instead. This has two benefits. First, you get some extra dependencies such as nose (testing package), sphinx (documentation package) and twine and wheel (used for releasing), etc. Second, you won’t have to reinstall the package into your Python site-packages directory every time you change something.

  1. Get Python 3. (Note: If you are on Windows it might be convenient to use a ready-made distribution like WinPython and skip step 5 below, but we can’t guarantee it will work.)

  2. Download the source code

  3. You probably want to install Friendly Sam in a virtual environment. Create one and activate it before you take the next step.

  4. Now, to install Friendly Sam in develop mode, do this:

    pip install -r develop.txt
    

Note

If you are on Windows, pip-installation of some packages will fail if you don’t have a compiler correctly configured. One such example is NumPy. A simple way around it is to install binaries from Christoph Gohlke’s website for the packages that throw errors when you do pip install -r develop.txt.

Let’s say you are on Windows and download an installer called something like numpy-MKL-1.9.0.win-amd64-py3.4.exe. Don’t just run the file, because then it will be installed in your “main” Python installation (usually at C:\Python34). Instead, do this:

  1. Open a command prompt.

  2. Go into your virtual environment (e.g. vex my_project_name cmd).

  3. (option a) Do this:

    easy_install numpy-MKL-1.9.0.win-amd64-py3.4.exe
    
  1. (option b) Or, if you have a wheels file something.whl, do this:

    pip install something.whl
    

Make Sphinx documentation

The documentation for residues is made with Sphinx and hosted with Read the Docs. To parse nice, human-readable docstrings, we use Napoleon.

  • If you want to make a very minor change to the documentation, you can actually just edit the source, push to the github repository and magically, the docs will update at readthedocs.org.

  • However, if you want to edit the docs a lot, you probably want to make test builds on your own machine. In that case, you need to learn about Sphinx. To build the docs, open a command prompt, go to friendlysam\docs and run the command:

    make html
    

The resulting HTML can be previewed under friendlysam\docs\_build\index.html.

Run tests

Please run the tests before pushing to the master branch.

To run all the tests, including doctests in the source code and doctests in this documentation, go to the project root directory and run:

nosetests --with-doctest --doctest-options=+ELLIPSIS

Releasing Friendly Sam

If Friendly Sam is installed in develop mode, you should already have twine (for secure communication with PyPI) and wheel (for building wheel distribution files).

  1. To put things on PyPI, you have to register on PyPI, and you should register on the test PyPI too:

  2. Make sure that your account is activated. You should get an email from PyPI.

  3. Make sure you are added as a maintainer of the friendlysam repository at PyPI/testPyPI.

  4. Create yourself a file called .pypirc and put it in your home directory. If you are on Windows, the file path should be``C:Usersyourusername.pypirc``. Put the following content in it:

    [distutils]
    index-servers =
        pypi
        test
    
    [pypi]
    repository:https://pypi.python.org/pypi
    username:your_pypi_username
    
    [test]
    repository:https://testpypi.python.org/pypi
    username:your_testpypi_username
    
  5. (Windows users) For Windows, there is a nice pypi.bat you can use.

    To register info about the package on PyPI, first push to the PyPI test site:

    pypi.bat register test
    

    You will be asked for your PyPI test password. Make sure it turned out as you wanted. Then do the real thing:

    pypi.bat register pypi
    

    To build and upload the distribution, do this:

    pypi.bat upload test
    

    Twine will upload to PyPI and ask you for username and password. Check on the test site that everything is OK. You can also run pip install ... from the test repo to be sure. Then upload the package to the real repo by running:

    pypi.bat upload pypi
    
  1. (Linux/Mac users) You can easily translate pypi.bat into a bash script. Please do so and contribute it to the repository!

What Friendly Sam is for

Why build another tool?

There are a lot of different tools for optimization-based modeling. Why in the world do we need another one?

The short answer is this: Friendly Sam is a domain specific toolbox. For the type of models we work with, the model code is shorter, more readable and easier to debug than it would be with many other tools. Furthermore, Friendly Sam makes data handling and analysis easier. Because Friendly Sam is implemented in Python, we get access to all our favorite Python tools for scientific computing and visualization, including Pandas, NumPy, SciPy, matplotlib, etc. This is a strong advantage because the majority of our modeling work is preparing input data and analyzing results.

In the coming paragraphs we’ll explain more about what Friendly Sam is. And at end we’ll also say a few things Friendly Sam is not.

Data handling is easier with Python

Friendly Sam was designed to simplify our work with optimization-based models of energy systems, so-called dispatch models. This is a common type of model in research and in applied analysis of energy systems, based on the thought that the operator(s) of an energy system always act so as to minimize the cost of delivering energy to customers, or maybe (in a parallel universe) to minimize the carbon emissions, or some other objective function. A dispatch model is usually formulated as a minimization problem: “Minimize the operation cost of this system in this time period, subject to the technical and legal constraints of the system.”

There are a zillion different variants of such models, but many of them have in common that there is a lot of data going in and out. Some examples of possible input data are prices for different forms of energy, demand profiles, technical constraints, etc. The output data could be operation decisions, system costs, greenhouse gas emissions, and many other things. Therefore, a large part of our modeling work is data handling: Reading and wrangling data files, transforming and resampling input and output data, visualizing results, making statistical tests, etc.

Many optimization-based models are implemented using a generic optimization modeling language like GAMS, AMPL, AIMMS or CMPL. These languages can be wonderful to work with when formulating models because they are made specifically for optimization, and they are efficient in transforming your human-readable code into something that can be understood by almost any optimization solver. However, the infrastructure for handling input and output data in GAMS and AMPL is sub-optimal (pun intended). Anyone who implemented a large, complicated model in one of those languages knows it’s not an easy ride to keep track of all the data going in and out, especially not if you want to make a lot of similar runs with different parameter sets. I know several people who wrote their own tools for getting inputs and outputs back and forth between GAMS and their favorite data crunching tool (Excel, Python, MATLAB, R, etc).

When we started writing what would later become Friendly Sam, we chose Python because of the great ecosystem of open source tools that come with it. We have paid specific attention to numpy, pandas, and matplotlib when developing Friendly Sam. It’s not necessary to use these tools with Friendly Sam, but there is a great chance they will make your life easier. What about optimization then? To formulate and solve the actual optimization problems, we first used the Python API of the Gurobi optimizer. Gurobi’s Python API exposes a Variable class with overloaded operators for addition, multiplication, etc, so you can make algebraic expressions for the optimization objective and all the constraints in Python code. The Gurobi backend then translates these expression objects into a well-formed optimization problem, solves the problem and delivers the solution back through the Python API so you never have to leave Python. In Friendly Sam 1.0 we have created an abstraction layer to reduce the dependence on a certain solver backend. We are now using PuLP to interact with the Gurobi and CBC solvers, but you never have to interact directly with the backend, and it is not too hard to switch to another backend if we want to.

Domain specific toolbox

Friendly Sam is a Python library for formulating, running, and analyzing optimization-based models of energy systems.

In fact, it’s not only suitable for modeling energy systems, but also for other systems where you want to optimize flow networks of physical or abstract quantities, be it energy carriers, money, solid waste, cargo deliveries, virtual water or something else.

In principle, you are not even restricted to modeling systems with flow networks, because the optimization engine behind Friendly Sam is exposed so you can formulate a large class of optimization problems. But if you want a generic tool for formulating optimization problems you should probably check out other tools instead. In Python it’s worth to look at CyLP, cvxpy, PuLP, and Pyomo. If you want a pure optimization language, look at GAMS, AMPL, AIMMS or CMPL.

So although Friendly Sam can be used as a rather generic optimization modeling tool, it is domain specific in the sense that it has vocabulary for energy systems and similar systems. We developed it specifically to help us formulate dispatch models. In our energy system models, there are almost always balance equations for energy or materials, so Friendly Sam contains definitions of things like FlowNetwork, Node and Cluster to simplify the formulation of such constraints. And the Node class is a perfect starting point for modeling things like power plants, energy storages, and other things you typically find in an energy system. Friendly Sam also has a simple formulation of a myopic dispatch model of the type we often encounter in the academic literature on energy system modeling. If you use these building blocks, you will have to think less about sign errors in balance equations and instead concentrate on what your model really means.

Friendly Sam code is meant to be readable. For example, in a district heating model we can have instances of Node subclasses, one named LinearCHP, another named HeatPump, etc. This makes perfect sense to us, because the code is naturally structured similar to how we think about the energy system we are modeling. When the underlying optimization problem is solved, we can query the state of the model objects with code like heat_pump.consumption['power'](time).

The code can also be easier to debug. When you have a bewildering error somewhere, it can be helpful to just eyeball the constraints of your optimization problem, to see if you can spot the error. Friendly Sam makes this easier by automatically naming constraints after their “owner”, for example the HeatPump instance we just mentioned. You can also name variables and add descriptions to constraints. These features help you understand where things come from when you are looking at a long list of constraints.

What Friendly Sam is not

First, we want to clarify that Friendly Sam is not “a model”. It is a toolbox we use to build models.

Second, Friendly Sam is not fool proof. It is entirely possible to make models that are stupid or wrong with Friendly Sam. We have tried to design Friendly Sam to produce readable, understandable, debuggable models, and to make idioms and conventions that help to avoid common errors. But having this tool is not an alternative to knowing and understanding the optimization problems you are creating. Friendly Sam is a tool to help us focus on what is important, rather than chasing indexing errors and how to formulate piecewise affine functions using special ordered sets.

Third, Friendly Sam is not primarily optimized for speed. If you want to solve a really big model fast, you are probably better off with something like AMPL or GAMS, or maybe writing your own code in a compiled language. However, if your model is moderately big you might get the job done faster with Friendly Sam because debugging, data handling, analysis and visualization will be so much faster. In our experience, the development phase often consumes more time and money than the computation phase, so development convenience is often more important than execution speed.

OK, let’s get started!

You are now ready to read about Variables and expressions.

Now that you know What Friendly Sam is for, let’s get started!

Variables and expressions

In Friendly Sam, each variable is an instance of the Variable class. Let’s create one:

>>> from friendlysam import Variable
>>> my_var = Variable('x')
>>> my_var
<friendlysam.opt.Variable at 0x...: x>
>>> print(my_var)
x

Variables can be added, multiplied, subtracted, and so on, to form expressions, including equalities and inequalities.

>>> expressions = [
...     my_var * 2 + 1,
...     (my_var + 1) * 2,
...     my_var * 2 <= 3
... ]
>>> for expr in expressions:
...     expr
<friendlysam.opt.Add at 0x...>
<friendlysam.opt.Mul at 0x...>
<friendlysam.opt.LessEqual at 0x...>
>>>
>>> for expr in expressions:
...     print(expr)
x * 2 + 1
(x + 1) * 2
x * 2 <= 3

Warning

The operator == is reserved for checking object similarity, just like we are used to in Python. To create the relation “x equals y”, use Eq:

>>> from friendlysam import Eq
>>> my_var == 1
False
>>> print(Eq(my_var, 1))
x == 1

There is also a nice Sum operation you should use for large sums. Using the built-in sum() will create a deeply nested and very inefficient tree of Add objects.

>>> from friendlysam import Sum
>>> many_terms = [my_var * i for i in range(100)]
>>> Sum(many_terms)
<friendlysam.opt.Sum at 0x...>
>>> sum(many_terms)
<friendlysam.opt.Add at 0x...>

Names don’t mean anything

In the example above, we named the Variable object 'x'. This is nothing more than a string attached to the object, and it does not say anything about the identity of the variable. In principle you can have several Variable objects with the same name, but that’s really confusing and should not be necessary.

>>> my_var = Variable('y')
>>> my_other_var = Variable('y')
>>> my_var == my_other_var
False
>>> print(my_var + my_other_var)
y + y

It is often a good idea to give your variables names you can recognize, because that simplifies debugging when you want to inspect the expressions you have made with the variables. But if you don’t want to name variables you don’t have to. The variables are then named automatically.

>>> Variable()
<friendlysam.opt.Variable at 0x...: x1>
>>> Variable()
<friendlysam.opt.Variable at 0x...: x2>

VariableCollection is like an indexed Variable

There is also a convenient class called VariableCollection. It is a sort of lazy dictionary, which creates variables when you ask for them:

>>> from friendlysam import VariableCollection
>>> z = VariableCollection('z')
>>> z
<friendlysam.opt.VariableCollection at 0x...: z>
>>> z(1)
<friendlysam.opt.Variable at 0x...: z(1)>
>>> z((1, 'a'))
<friendlysam.opt.Variable at 0x...: z((1, 'a'))>
>>> z(None)
<friendlysam.opt.Variable at 0x...: z(None)>

You can think of VariableCollection as an indexed variable, but all it really does is to create variables when you call it, and then remember them.

The index must be hashable. For example, tuples are valid indices, but not lists:

>>> z((3, 1, 4))
<friendlysam.opt.Variable at 0x...: z((3, 1, 4))>
>>> z([3, 1, 4])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'

Variables can be named in a namespace, like this:

>>> from friendlysam import namespace
>>> with namespace('cheese'):
...     cheese1 = Variable('gorgonzola')
...     cheese2 = VariableCollection('ricotta')
...
>>> cheese1
<friendlysam.opt.Variable at 0x...: cheese.gorgonzola>
>>> cheese2
<friendlysam.opt.VariableCollection at 0x...: cheese.ricotta>

The namespace doesn’t affect the function of a variable in any way. It only prepends a string representation of whatever object to the variable name, so you can also do things like this:

>>> with namespace(dict()):
...     Variable('x')
...
<friendlysam.opt.Variable at 0x...: {}.x>

Variables can have values

You can assign a value to a variable. The variable will still work in expressions:

>>> x = Variable('x')
>>> x.value = 39
>>> expression = x + 3
>>> expression
<friendlysam.opt.Add at 0x...>
>>> print(expression)
x + 3

The difference is that you can now evaluate expressions. But note that the expression object is unchanged.

>>> float(expression)
42.0
>>> print(expression)
x + 3

You can change or delete the value:

>>> x.value = 0.5
>>> int(expression)
3
>>> float(expression)
3.5
>>> expression.value
3.5
>>> del x.value
>>> float(expression)
Traceback (most recent call last):
...
friendlysam.opt.NoValueError: cannot get a numeric value: x + 3 evaluates to x + 3

And it works for relations, too:

>>> x.value = 10
>>> (x <= 12).value
True

Expressions are immutable

Expressions are hashed by structure: If they do the same thing, they hash and compare equal. This also means they are considered equal e.g. as dict keys.

>>> expr1 = x * 2
>>> expr2 = x * 2
>>> expr1 is expr2 # Different objects!
False
>>> expr1 == expr2 # But similar
True
>>> d = dict()
>>> d[expr1] = 'some value'
>>> d[expr2]
'some value'

Expressions are immutable, meaning that their state can never be changed. In the example above, expr1 == expr2 and that will always be true. Two expressions are interchangeable if (and only if) they compare equal. For any purpose, in any situation, expr1 will always do the same thing as expr2.

However, as you saw above, the result of float(expr1) may vary depending on whether variables in the expression have values. Let’s look a little bit closer:

>>> x.value = 3
>>> expression = x + 39
>>> float(expression)
42.0
>>> x.value = 100
>>> another_expression = x + 39
>>> expression == another_expression
True
>>> float(expression)
139.0
>>> float(another_expression)
139.0

This is pretty much analogous to a tuple of mutable objects. The tuple itself may never change, but its contents may:

>>> a = [1, 2, 3]
>>> my_tuple = (a, 'something')
>>> my_tuple
([1, 2, 3], 'something')
>>> a[:] = ['changed'] # Only changing the contents of the list
>>> another_tuple = (a, 'something')
>>> my_tuple == another_tuple
True
>>> my_tuple
(['changed'], 'something')
>>> another_tuple
(['changed'], 'something')

Behind value is evaluate()

You might want to know what is happening behind the scenes when you ask for expression.value or float(expression). In that case, check out the method evaluate().

Optimization problems

Creating a problem

We use Friendly Sam to formulate MILP problems. The optimization library could be extended to allow other types of problems, too, but this is what is supported today.

Now, let’s begin with a full example of an optimization problem.

>>> import friendlysam as fs
>>>
>>> # Create the problem
>>> x = fs.VariableCollection('x')
>>> prob = fs.Problem()
>>> prob.objective = fs.Maximize(x(1) + x(2))
>>> prob.add(8 * x(1) + 4 * x(2) <= 11)
>>> prob.add(2 * x(1) + 4 * x(2) <= 5)
>>>
>>> # Get a solver and solve the problem
>>> solver = fs.get_solver()
>>> solution = solver.solve(prob)
>>> type(solution)
<class 'dict'>
>>> solution[x(1)]
1.0
>>> solution[x(2)]
0.75

The solver does not in any way affect the problem or the variables. It just reads the problem, solves it and handles back a dict with your Variable objects as keys and their solutions as values.

If you set the value of some variables, those will be inserted into the problem before solving it:

>>> x(1).value = 0
>>> solution = solver.solve(prob)
>>> solution
{<friendlysam.opt.Variable at 0x...: x(2)>: 1.25}
>>> x(1) in solution
False

x(1) is not in the solution, because you already set its value, so it was handled like a number by the solver.

Debugging constraints

Now let’s add another constraint:

>>> x(1).value = 0
>>> prob.add(1 <= x(1))
>>> solver.solve(prob)
Traceback (most recent call last):
...
friendlysam.opt.ConstraintError: The expression in <Constraint: Ad hoc constraint> evaluates to False, so the problem is infeasible.

In this case it’s obvious why the problem could not be solved. But for argument’s sake, let’s say we didn’t know which constraint was causing a problem. The error message was not too helpful, but the ConstraintError luckily also contains a reference to the constraint that failed, so we can pick it out like this:

>>> try:
...     solver.solve(prob)
... except fs.ConstraintError as e:
...     failed_constraint = e.constraint
...     print(repr(failed_constraint))
...     print(repr(failed_constraint.expr))
...     print(failed_constraint.expr)
...     print(failed_constraint.desc)
...     print(failed_constraint.origin)
...
<friendlysam.opt.Constraint at 0x...>
<friendlysam.opt.LessEqual at 0x...>
1 <= x(1)
Ad hoc constraint
None

OK, that’s helpful! We got the problematic constraint out. And there are a few things you should note.

  1. The type of the failed constraint is friendlysam.opt.Constraint. It was automatically created when we added a friendlysam.opt.LessEqual constraint to the problem, and its sole purpose is to wrap the inequality 1 <= x(1) and to add some metadata.
  2. The Constraint object contains the LessEqual object that we added to the problem.
  3. The Constraint object contains also a description desc and a variable called origin which is supposed to say something about where the constraint comes from.

If you want to make your model easier to debug, you can add Constraint instances to your problems, like in this stupid example:

>>> from friendlysam import Constraint
>>> def constr(var, parameter):
...     return var / 42 >= parameter
>>> for i in range(5):
...     expr = constr(x(i), i)
...     origin = (constr, x(i), i)
...     prob += Constraint(expr, desc='Some description', origin=origin)
...

Different ways to add constraints

Note

In the examples above, we added constraints like this:

>>> prob.add(8 * x(1) + 4 * x(2) <= 11)
>>> prob += Constraint(expr, desc='Some description', origin=origin)

These two methods are equivalent, so just choose the syntax you like best.

You can also send an iterable (even a generator), and the items in the iterable can also be iterables, e.g:

>>> prob += ([constr(x(i), i), constr(x(i+1), i)] for i in range(5))

See the documentation for add() for all the details.

Code reference

friendlysam.opt

class friendlysam.opt.Add

Addition operator.

See Operation for a general description of operations.

Parameters:*args – Should be exactly two terms to add.

Examples

>>> x = VariableCollection('x')
>>> expr = x(1) + x(2)
>>> expr
<friendlysam.opt.Add at 0x...>
>>> expr == Add(x(1), x(2))
True
>>> x(1).value, x(2).value = 2, 3
>>> float(expr)
5.0
class friendlysam.opt.Constraint(expr, desc=None, **kwargs)

docstring for Constraint

exception friendlysam.opt.ConstraintError(*args, **kwargs)

docstring

class friendlysam.opt.Domain

Domain of a variable.

Variable and VariableCollection support these domains passed in with the domain keyword argument of the constructor.

Examples

>>> s = get_solver()
>>> prob = Problem()
>>> x = Variable('x', domain=Domain.integer)
>>> prob.objective = Minimize(x)
>>> prob += (x >= 41.5)
>>> solution = s.solve(prob)
>>> solution[x] == 42
True
class friendlysam.opt.Eq

The relation “equals”.

Warning

This operation does not have overloaded operators for creation, so instead you should use the constructor, Eq(a, b).

Examples

>>> x = Variable('x')
>>> x == 3 # Don't do this!
False
>>> equality = Eq(x, 3) # Do this instead.
>>> equality
<friendlysam.opt.Eq at 0x...>
>>> x.value = 3
>>> equality.value
True
>>> x.value = 4
>>> equality.value
False
class friendlysam.opt.Less

The relation “less than”.

Examples

>>> x = Variable('x')
>>> expr = (x < 1)
>>> expr
<friendlysam.opt.Less at 0x...>
>>> expr == Less(x, 1)
True
>>> x.value = 1
>>> expr.value
False

Note

There is no Greater class, but you can use the overloaded operator >.

>>> x > 1
<friendlysam.opt.Less at 0x...>
>>> print(_)
1 < x
>>> (x > 1) == (1 < x)
True
class friendlysam.opt.LessEqual

The relation “less than or equal to”.

Examples

>>> x = Variable('x')
>>> expr = (x <= 1)
>>> expr
<friendlysam.opt.LessEqual at 0x...>
>>> expr == LessEqual(x, 1)
True
>>> x.value = 1
>>> expr.value
True

Note

There is no GreaterEqual class, but you can use the overloaded operator >=.

>>> x >= 1
<friendlysam.opt.LessEqual at 0x...>
>>> print(_)
1 <= x
>>> (x >= 1) == (1 <= x)
True
class friendlysam.opt.Maximize(expr)

docstring for Maximize

class friendlysam.opt.Minimize(expr)

docstring for Minimize

class friendlysam.opt.Mul

Addition operator.

See Operation for a general description of operations.

Parameters:*args – Should be exactly two terms to multiply.

Examples

>>> x = VariableCollection('x')
>>> expr = x(1) * x(2)
>>> expr
<friendlysam.opt.Mul at 0x...>
>>> expr == Mul(x(1), x(2))
True
>>> x(1).value, x(2).value = 2, 3
>>> float(expr)
6.0

Note

There is currently no division operator, but the operator / is overloaded such that x = a / b is equivalent to x = a * (1/b). Hence, you can do simple things like

>>> print(x(1) / 4)
x(1) * 0.25
exception friendlysam.opt.NoValueError

Raised when a variable or expression has no value.

class friendlysam.opt.Operation

An operation on some arguments.

This is a base class. Concrete examples:

Arithmetic operations: Add, Sub, Mul, Sum

Relations: Less, LessEqual, Eq

Note

The Variable class and the arithmetic operation classes have overloaded operators which create Operation instances.

Examples

>>> x = Variable('x')
>>> isinstance(x * 2, Operation)
True
>>> x + 1
<friendlysam.opt.Add at 0x...>
args

The arguments of the operation.

See create().

Examples

>>> x, y = Variable('x'), Variable('y')
>>> expr = x + y
>>> expr
<friendlysam.opt.Add at 0x...>
>>> expr.args == (x, y)
True
>>> (x + y) * 2
<friendlysam.opt.Mul at 0x...>
>>> _.args
(<friendlysam.opt.Add at 0x...>, 2)
classmethod create(*args)

Classmethod to create a new object.

This method is the default evaluator function used in evaluate(). Usually you don’t want to use this function, but instead the constructor.

Parameters:*args – The arguments the operation operates on.

Examples

>>> x = Variable('x')
>>> args = (2, x)
>>> Add.create(*args) == 2 + x
True
>>> LessEqual.create(*args) == (2 <= x)
True
evaluate(replace=None, evaluators=None)

Evaluate the expression recursively.

Evaluating an expression:

1. Get an evaluating function. If the class of the present expression is in the evaluators dict, use that. Otherwise, take the create() classmethod of the present expression class.

2. Evaluate all the arguments. For each argument arg, first try to replace it by looking for replace[arg]. If it’s not there, try to evaluate it by calling arg.evaluate() with the same arguments supplied to this call. If arg.evaluate() is not present, leave the argument unchanged.

  1. Run the evaluating function func(*evaluated_args) and return the result.
Parameters:
  • replace (dict, optional) – Replacements for arguments. Arguments matching keys will be replaced by specified values.
  • evaluators (dict, optional) – Evaluating functions to use instead of the default (which is the create() classmethod of the argument’s class). An argument whose __class__ equals a key will be evaluated with the specified function.

Examples

>>> x = VariableCollection('x')
>>> expr = x(1) + x(2)
>>> print(expr.evaluate())
x(1) + x(2)
>>> expr.evaluate(replace={x(1): 10, x(2): 20})
<friendlysam.opt.Add at 0x...>
>>> print(_)
10 + 20
>>> expr.evaluate(replace={x(1): 10, x(2): 20}, evaluators=fs.CONCRETE_EVALUATORS)
30
leaves

The leaves of the expression tree.

The leaves of an Operation are all the args which do not themselves have a leaves property.

Examples

>>> x, y = Variable('x'), Variable('y')
>>> expr = (42 + x * y * 3.5) * 2
>>> expr.leaves == {42, x, y, 3.5, 2}
True
value

The concrete value of the expression, if possible.

This property should only be used when you expect a concrete value. It is computed by calling evaluate() with the evaluators argument set to CONCRETE_EVALUATORS. If the returned value is a number or boolean, it is returned.

Raises::excNoValueError if the expression did not evaluate to a number or boolean.
variables

All leaves which are instances of Variable.

Examples

>>> x, y = Variable('x'), Variable('y')
>>> expr = (42 + x * y * 3.5) * 2
>>> expr.variables == {x, y}
True
class friendlysam.opt.Problem(constraints=None, objective=None)

An optimization problem

add(*additions)

docstring

solve()

Try to solve the optimization problem

class friendlysam.opt.Relation

Base class for binary relations.

See child classes:

class friendlysam.opt.SOS1(variables, **kwargs)

docstring for SOS1

class friendlysam.opt.SOS2(variables, **kwargs)

docstring for SOS2

exception friendlysam.opt.SolverError

A generic exception raised by a solver instance.

class friendlysam.opt.Sub

Addition operator.

See Operation for a general description of operations.

Parameters:*args – Should be exactly two items to subtract.

Examples

>>> x = VariableCollection('x')
>>> expr = x(1) - x(2)
>>> expr
<friendlysam.opt.Sub at 0x...>
>>> expr == Sub(x(1), x(2))
True
>>> x(1).value, x(2).value = 2, 3
>>> float(expr)
-1.0
class friendlysam.opt.Sum

A sum of items.

See the base class Operation for a basic description of attributes and methods.

args

A tuple of items to be summed.

Examples

Note that the constructor takes an iterable of arguments, just like the built-in sum() function, but the classmethod create() takes a list of arguments, as follows.

>>> x = VariableCollection('x')
>>> terms = [x(i) for i in range(4)]
>>> Sum(terms) == Sum.create(*terms)
True
>>> s = Sum(terms)
>>> s.evaluate(evaluators={Sum: sum})
Traceback (most recent call last):
...
TypeError: sum expected at most 2 arguments, got 4
>>> s.evaluate(evaluators={Sum: lambda *args: sum(args)})
<friendlysam.opt.Add at 0x...>
static __new__(vector)

Create a new Sum object

Parameters:vector (iterable) – The items to sum. Can be any iterable, also a generator, and may be zero length.
classmethod create(*args)

Classmethod to create a new Sum object.

Note that create() has a different signature than the constructor. The constructor takes an iterable as only argument, but create() takes a list of arguments.

Example

>>> x = VariableCollection('x')
>>> terms = [x(i) for i in range(4)]
>>> Sum(terms) == Sum.create(*terms)
True
class friendlysam.opt.Variable(name=None, lb=None, ub=None, domain=<Domain.real: 0>)

A variable to build expressions with.

Parameters:
  • name (str, optional) – A name of the variable. It has no relation to the identity of the variable. Just a name used in string representations.
  • lb (number, optional) – If supplied, a lower bound on the variable in optimization problems. If not supplied, the variable is unbounded downwards.
  • ub (number, optional) – If supplied, an upper bound on the variable in optimization problems. If not supplied, the variable is unbounded upwards.
  • domain (any of the Domain values) – The domain of the variable, enforced in optimization problems.

Note

The name, lb, ub and domain can also be set as attributes after creation.

>>> a = Variable('a')
>>> a.lb = 10
>>> a.Domain = Domain.integer

is equivalent to

>>> a = Variable('a', lb=10, domain=Domain.integer)

Examples

The namespace() context manager can be used to conveniently name groups of variables.

>>> with namespace('dimensions'):
...     w = Variable('width')
...     h = Variable('height')
...
>>> w.name, h.name
('dimensions.width', 'dimensions.height')
evaluate(replace=None, evaluators=None)

Evaluate a variable.

See Operation.evaluate() for a general explanation of expression evaluation.

A Variable is evaluated with the following priority order:

  1. If it has a value, that is returned.

2. Otherwise, if the variable is a key in the replace dictionary, the corresponding value is returned.

  1. Otherwise, the variable itself is returned.
Parameters:
  • replace (dict, optional) – Replacements.
  • evaluators (dict, optional) – Has no effect. Just included to be compatible with the signature of Operation.evaluate().

Examples

>>> x = Variable('x')
>>> x.evaluate() == x
True
>>> x.evaluate({x: 5}) == 5
True
>>> x.value = -1
>>> x.evaluate() == -1
True
>>> x.evaluate({x: 5}) == -1 # .value goes first!
True
>>> del x.value
>>> x.value
Traceback (most recent call last):
...
friendlysam.opt.NoValueError
take_value(solution)

Try setting the value of this variable from a dictionary.

Set self.value = solution[self] if possible.

Raises:KeyError if ``solution[self]`` is not available.
value

Value property.

Warning

There is nothing stopping you from setting value to a value which is inconsistent with the bounds and the domain of the variable.

class friendlysam.opt.VariableCollection(name=None, **kwargs)

docstring for VariableCollection

friendlysam.opt.get_solver(engine='pulp', options=None)

Get a solver object.

Parameters:
  • engine (str, optional) – Which engine to use.
  • options (dict, optional) –

    Parameters to the engine constructor.

    If engine == 'pulp', the engine is created using PulpSolver(options). See PulpSolver constructor for details.

friendlysam.opt.namespace(name)

Context manager for prefixing variable names.

Examples

>>> with namespace('dimensions'):
...     w = Variable('width')
...     h = VariableCollection('heights')
...
>>> w
<friendlysam.opt.Variable at 0x...: dimensions.width>
>>> h(3)
<friendlysam.opt.Variable at 0x...: dimensions.heights(3)>

friendlysam.parts

class friendlysam.parts.Cluster(*parts, **kwargs)

docstring for Cluster

class friendlysam.parts.ConstraintCollection(owner)

docstring for ConstraintCollection

class friendlysam.parts.FlowNetwork(resource, **kwargs)

docstring for FlowNetwork

class friendlysam.parts.Node(**kwargs)

docstring for Node

class friendlysam.parts.Part(name=None)

docstring for Part

iter_times_between(start, end)

Only works if time is orderable!!

times_between(start, end)

Only works if time is orderable!!

class friendlysam.parts.Storage(resource, capacity=None, maxchange=None, **kwargs)

docstring for Storage

friendlysam.models

class friendlysam.models.MyopicDispatchModel(t0=None, horizon=None, step=None, name=None, require_cost=True)

docstring for MyopicDispatchModel

friendlysam.common

friendlysam.compat

friendlysam.solvers

class friendlysam.solvers.pulpengine.PulpSolver(options)

docstring for PulpSolver

friendlysam.solvers.pulpengine.maketrans()

Return a translation table usable for str.translate().

If there is only one argument, it must be a dictionary mapping Unicode ordinals (integers) or characters to Unicode ordinals, strings or None. Character keys will be then converted to ordinals. If there are two arguments, they must be strings of equal length, and in the resulting dictionary, each character in x will be mapped to the character at the same position in y. If there is a third argument, it must be a string, whose characters will be mapped to None in the result.

Indices and tables