Sunday, January 18, 2015

Vectors, Tensors and Matrices in the Kitchen

This is an example I made as a teacher. The idea was to take the vector and matrix away from the usual application in space to something more everyday: the kitchen. It is a humorous application of mathematical thinking to baking cakes. It touches the essentials of Linear algebra, without getting too formal.

It can also be found on my automatic exercise correcting, course building site called mamchecker based on google appengine. But here I have extended the idea a little bit.

Vectors, Tensors and Matrices in the Kitchen

Vectors

Let's talk about the ingredients of a cake, like eggs.

Eggs is a variable. We can take one egg, or two eggs, or three eggs,... The values are exclusive. We cannot take two eggs and three eggs for the same cake.

variable = set of exclusive values in a context

Three eggs. Three is the number. Egg gives meaning to the number. Egg is the unit. Eggs is a coordinate system (CS), that gives meaning to the numbers.

Three is the contravariant part. Egg, the unit, is the covariant part, of eggs. Note that we could take another unit, like sixpacks of eggs. Then the covariant part is bigger, but the contravariant part becomes smaller. This is already a coordinate transformation.

0.5 kg of flour. Another ingredient, another variable.

One variable is also called one-dimensional (1D) space. Two variables, from which we can choose independently, make up a two-dimensional space (2D), and so on.

Why introduce another word dimension and not just continue with variables? Actually, it's just to stick a little with conventional usage. Dimensions and variables are the same here.

A set of values from one or more variables is called a vector

  • if these values can be added independently, i.e. eggs with eggs, ...
  • and if they can be multiplied with numbers. I we add twice the same vector v we should be able to say 2v . Then all components of the vector are multiplied by 2.

The cake ingredients are a vector space over the real numbers .

Eggs, Flour, Butter, Sugar, ... they are not exclusive when baking a cake. Then they are not values of a variable, they are just a set. Let i be any of them and C be a cake. Ci is the contravariant number value to the covariant unit Ci . i is the index that indicates, which ingredient.

C = CiCi = CiCi

: If there are two same indices, then these are summed by Einstein's convention.

This is like writing 'Ten Eggs and one unit of flour and ...`.

If we do that a lot, it becomes tiresome. We tend to make a table then. If we always make the same table, we remember the positions of eggs and flour, ... (position coding) and only write the numbers. A vector, though, is not just the numbers. It is the real thing, the cake recipe, in this example.

dot product

Eggs have nothing in common with flour, well, at least if we don't dig too deep, because they surely have carbon atoms in common. But for our kitchen thinking they have nothing in common. In mathematics this is orthogonal.

There is an operation between two vectors, that tells about how much they have in common: the inner product or dot product or skalar product. It's result is a scalar, i.e. a number. One denotes it by a dot in between, or by  < Egg|Flour >  .  < Egg|Egg >  = 1 , because they have all in common.  < Egg|Flour >  = 0 , because they have nothing in common (orthogonal).

In a cake recipe the units of ingredients are all orthogonal to each other: CiCj = 0 , if j ≠ j . I used the dot notation here. They are also of unit 1, which makes them orthonormal.

The ingredients are the basis of the cake. The unit ingredients are the orthonormal basis vectors.

Though convenient it is not a necessity that the basis vectors have nothing in common, i.e. are orthogonal.

For an example let's move from the ingredients of cakes (ingredient vector space) to the cake vector space. Every cake is a unit to sell or bring to a party. And every cake is an ingredient vector, an independent choice from more variables. Cake A and cake B surely have ingredients in common. So these unit vectors in the cake vector space are not orthogonal to each other. The dot product is not 0.

AiAiBjBj = AiBi ≠ 0

All the terms with i ≠ j are 0, because of the orthonormal basis. So they have been dropped after the first  =  . Then we have the usual formula for the dot product.

If AiBj were not 0 for i ≠ j , we would have a AiBj , called curvature tensor. As you can see it results from vectors, but cannot be added any more. That is the reason for the different name.

Coordinate Transformation

A vector in the cake vector space - How many of each kind of cake? Let's call it a cake assortment - can be transformed to the ingredient vector space by multiplying with a matrix. Every matrix column is the recipe of one kind of cake. The columns are the basis vectors of the cake space. By multiplying the assortment vector with the transformation matrix, we do a linear combination of the cake vectors.

A = AjAj = AjCjiCi = CiCi

The transformation matrix Cji says how much of the i ingredient cake j needs. j is the lower index and is a column. i is the upper index and it is a row. Ci is Egg, Flour, ... Aj is the amount of cake Aj in the assortment A . With Aj and Ci implicit, the transformation is:

Ci = CjiAj

The summation over the j index is the matrix multiplication. It results in a number in each row telling about the total amount of one ingredient, like eggs.

On a matrix row there is an amount of an ingredient, like egg, for every kind of cake. This is the dot product  < egg|cakej >  . In general, if we transform from a CS with basis vectors Aj to a CS with basis vectors Ci the transformation matrix is CiAj , j being the columns.

A = AjAj = Aj < Ci|Aj > Ci

Inverse

If the number of is and the number of js are equal it is possible to find Aj from Ci , the number of each kind of cake in the assortment, from the total amount of ingredients used.

In this example, though, the cake space and ingredient space do normally not have the same number of variables (number of variables = dimension). If we can have 10 ingredients and 3 kinds of cakes. Then the transformation matrix is 10x3 (10 rows, 3 columns). Such a m × n matrix with m ≠ n cannot be inverted, i.e. one cannot infer from the ingredients how many of each kind of cake are baked. Said differently: Not for every combination of ingredients there is a combination of cakes, which needs exactly this amount of ingredients.

Note

A non-square matrix can be pseudo-inverted, though: Moore-Penrose Pseudoinverse. For this example multiplying an ingredient vector with the pseudo-inverse would produce a cake vector, which minimizes unused quantities of ingredients (Method of Least Squares) or makes best use of the ingredients (Maximum Entropy Method).

If we change from one vector space to another with same dimensions, then we can get back to the starting one by multiplying with the inverse matrix (A − 1 ). A − 1 matrix multiplied by A gives the identity matrix I . Calculating the inverse means solving a system of linear equations.

Ax  = y A − 1Ax  = A − 1y Ix  = A − 1y x  = A − 1y

In order for the inverse to exist, in addition to being square, the columns/rows of the matrix must be linearly independent. If not, then that is like effectively being in a smaller matrix (rank of matrix). For the cake example this means that every kind of cake must have a different combination of ingredients, which is some extra information that distinguishes it from the others and that can be used to code something.

Note

A square matrix can be inverted, if columns (rows) cannot be expressed as linear combination of the others, i.e. the rank of the matrix is equal to its dimension.

One can calculate the inverse of a square Matrix by:

  • leaving out the ij cross and calculate the determinant = Minor Mij
  • change the sign, if i + j is odd
  • transpose, i.e. mirror at the main diagonal (compare below: ij for A and ji for M )
  • divide everything by the determinant

Short:

(A − 1)ij = (1)/(det(A))( − 1)i + jMji

Inverse of a 2x2 Matrix:

Mij is the diagonally opposite number. Because of the transposing the numbers left bottom and right top (secondary diagonal) stay where they are, but the sign changes. At the main diagonal the numbers get swapped, but since i + j is even the sign does not change.

  • Main diagonal  →  swap, keep sign
  • Secondary diagonal  →  no swap, change sign

There are algorithms, though, that are more efficient to calculate the inverse. And, of course, we use a computer program to do such calculations.

Sunday, January 11, 2015

(Complex) Numbers

(Complex) Numbers

(Complex) Numbers

This is partly from my time as a teacher, when I made an automatic exercise correcting, course building site called mamchecker based on google appengine.

I go through the different number sets in a not so standard way and conclude with the complex numbers, which have always been fascinating to me, because somehow enigmatic, not obvious, and then because useful and a good example of mathematical thinking.


Natural Numbers ( ) by themselves are a great abstraction. Forget about all qualities but the "how many", the cardinality.

Then, do we add apples or do we take them away? So the sign was invented and the Integers ( ). We write -2, but since this linear direction is a concept by itself, we should write 2( − ) , that is 2 times ( − ) , like we write 2m or 2g . And when we want to say three things (how many, add or remove, what quantity), then we could write 2( − )g . We could also give ( − ) a name like s for subtract (remove) and then we should also have a for add. But to have special signs ( −  ,  +  ), not a letters, is a lot better. So these signs make the add and subtract operations part of the natural numbers, making them integers and operations. Instead of 2( − ) we write  − 2 , but we should still think of it as two times subtracting.  + 3 − 2 is a sequence of such operations, we could emphasize this by writing  + 3,  − 2 .

The same reasoning we can do with * (multiplying) and its opposite  ⁄  (dividing). *2 and  ⁄ 2 are elements of the rationals ( ). *3 ⁄ 2*5 is a sequence of such operations (*3,  ⁄ 2, *5 ). Most computer programs don't understand *3 ⁄ 2*5 unless the initial * or  ⁄  is omitted. For  +  and  −  they do understand.

Addition and subtraction are two elements of a set, i.e. of something we can pick one exclusively at a time. I normally call such a thing a variable and the pick a value. This variable is often called after one value: Addition = { + ,  − } . Same for Multiplication = {*,  ⁄ } .

When combining addition and multiplication in a sequence, we have also introduced a convention: multiplication before addition. This operator precedence rule allows us to write 2*3 + 2*4 in one expression instead of a = 2*3, b = 2*4, a + b .

To put multiplication first was a good choice. We have had multiplication above when we wrote 2( − ) to emphasize what  − 2 means. There is something very basic about multiplication: Whenever there are two variables, that have nothing in common, that we can pick from independently, that are orthogonal, we simple pick values independently and place them one after the other. When we count the possibilities of combined picks we get |A|*|B| , where || is the cardinality, the information content. This is why we write ab for the area of an a × b rectangle and why physicists combine all kinds of variables via multiplication. These variables are extensive meaning any value of it is a set. E.g. the length of a rectanble side is the set of all its points or of all its unit stretches. I also call such variables quantities here.

Next come the irrational numbers (I ). For example no sequence of multiplication and division with natural numbers will produce the diagonal from the edge of a square (incommensurable, (2) ), but one can get as close as wanted. This is how they are defined: An irrational is an infinite sequence of rationals. In this sense infinity is an irrational.

Irrationals and rationals make up the real numbers ( ).

Several times numbers were augmented with new information. The same we do with to the real numbers to get to the complex numbers. In a real number we have the number, multiplication operation (* ,  ⁄  ) and the linear direction ( +  ,  −  ). We normally first determine the quantity by comparing to a unit (multiply e.g. meters) and then optionally add it (to 0 on default).

There is another concept, which we can introduce: orthogonality. This "variable" contains 1 (normally omitted) for same direction and i for orthogonal or turned by right angle π ⁄ 2 , counterclockwise by convention. Turning a second time gives i*i =  − 1 . This solves x2 + 1 = 0 , starting from which normally i is actually introduced. 2ia is 2a and orthogonal to the direction of a .

i is called imaginary unit and ℝ × {i} are called imaginary numbers. Along this reasoning one should have called them orthogonal numbers. By addition they are independent/orthogonal to the real numbers. Together with the real numbers they make the complex numbers ( ) Orthogonal means that all combinations are possible, which corresponds to a 2-dimensional (2D) plane, the complex plane or Gauss number plane.

z = (a, b) = a + ib ∈ ℂ
  • a = Re(z) is the real part
  • b = Im(z) is the imaginary part

These numbers are like 2D-vectors: 2 orthogonal directions that can be added independently.

There are three representations

  • z = a + ib , i.e. via the components or
  • z = r(cosφ + isinφ) via modulus r and argument φ (angle, phase) in radiants.

Note that, by the trigonometric addition formulas, multiplication adds the angles, i.e. multiplication leads to addition. This gives a hint that there could be a representation that has the angle in the exponent. Developing sin and cos into a Taylor series and comparing with the ex series leads to the Euler Formula:

eiφ = cosφ + isinφ
  • z = reiφ is the third way to represent complex numbers.

    • φ = arg(z) is the argument (phase) of z.
    • arg(yz) = arg(y) + arg(z)
    • arg((y)/(z)) = arg(y) − arg(z)

About sin and cos we know that the period is 2π , therefore this is true for eiφ . The nth root divides the period up to 2nπ to below 2π and so we have n different roots.

z1 ⁄ n = r1 ⁄ nei(φ ⁄ n + 2kπ ⁄ n)

More generally:

In every polynomial of degree n has exactly n roots (fundamental theorem of algebra), if one counts the multiplicity of roots. therefore is called algebraically closed.

This means that not only x2 , but every polynomial maps the whole to the whole of . This closedness is important. All operations are reversible. One can calculate freely.

z = re − iφ = a − ib is the complex conjugate of z. (zn) = zn . We know that there are two orthogonals. We've chosen i to be one of them, so  − i is the other.

yz combines in itself dot product (Re(yz) = |y||z|cosΔφ ) and cross product (Im(yz) = |y||z|sinΔφ ). dot and cross are two aspects of orthogonality (=combinability). dot being 0 means there is no dependence, which is saying all combinations are possible, which means the combined variable is of maximum cardinality (area, cross), i.e. a rectangle. The conjugate is important, because it produces the delta angle between y and z . Angle again is another, i.e. third way of expressing dependence. With yz dot, cross and angle are beautifully combined.

|z| = (zz) = (a2 + b2) = r is the absolute value (modulus) of z . Here the imaginary part, the cross has gone, because there is no area in between. And why the () ? Multiplication brought us to the combined variable, but in this limit case of 0 angle any independent other variable has gone and thus we go back to the the length, the cardinality, the information of z alone.

Why is the pythagorean theorem in there. To understand this we need to understand the fundamentals of the pythagorean theorem. There are two orthogonal variables involved. Let's name their units j and k instead of 1 and i (m and s for length and time,...). z = z1j + z2k is one value of the combined variable. The very fundamental dot of y and z reduces to the dot between j and k via yz = (y1j + y2k)⋅(z1j + z2k) = y1z1jj + y2z2kk + y1z2jk + y2z1kj . Now j and k are orthogonal, so jk = kj = 0 , j and j are the same, thus jj = 1 . Same with kk = 1 . What we get is yz = y1z1 + y2z2 , which is the pythagorean theorem if y = z .

number times unit is a special team: the quantity and the quality. It is also called a vector space. If the number is complex, it is a vector space over complex numbers. A vector can also subsume independent quantities, making it of dimension n > 1. A complex number z already has orthogonality. In a 2D vector space of units j and k it should be ij = k , because there is no other dimension. Actually something like this is done with quaternions. But for a general 2D vector space each component with becomes 2D itself. So a nD vector space over becomes kind of 2nD dimensional. What does is to allow specifying: can be added or cannot be added. This is comparable to a boolean.

To get the orthogonality, the dot, of two elements of such a 2D space over we do (uj + vk)⋅(yj + zk) = uy + vz = uy + vz , i.e. we take the conjugates. We need because we know there is orthogonality in a complex number and we've seen above how to deal with it. In general with r, s ∈ ℂn the orthogonality is the real part of rs = risi .


Now, do we need the complex numbers? Yes. The main reason is that orthogonality is a very important concept and so complex numbers find application in many fields. They make notation and calculation so much easier.

One essential point is the comparison of two quantities regarding addability. If they are parallel they are addable, if they are orthogonal, they are not. To this end paramters that have influence on addability, can be mapped to an angle (phase), which with Euler's notation of complex numbers, become a parallel (addable) and a orthogonal (not addable) part.

Examples:

  • The time t of a vibration becomes φ = (2π)/(T)t or
  • the t , x positions of a wave become φ = (2π)/(λ)x + (2π)/(T)t .

Re(Aeiφ) then represents the addable amplitude.

Sunday, January 4, 2015

SCons

The purpose of this post is to get acquainted with the cross-platform build automation system SCons.

Why SCons

In the past I've looked at boost-build and waf.

boost-build has a huge code base in its own scripting language jam. I don't want to learn that language. It is not general purpose. So in case of troubles I won't be able to dig into it. CMake, by the way, too, has its own scripting language.

It needs to use a general purpose scripting language. Since I'm used to Python I go for something in Python.

waf was too much work-in-progress, so it didn't have building knowledge implemented yet. It is actively developed, though, and I will look at it later again.

SCons and the build specification in a SConscript is written in Python. SCons knows about many tools. What I miss most about it, though, is that it is not Python 3 compatible yet.

The SCons user guide and man page gives a comprehensive coverage. But I feel more at ease if I have a rudimentary understanding of how it is implemented. Then I can look up things in the primary source of information, the code. It also helps in case of troubles.

The SCons code is at bitbucket.

Scons code

SConstruct and SConscript

SConstruct, the starting SConscript, is expected in the current directory, or somewhere above with -u.

SConscipt is normal python code, which has at its disposal globals like Program, SharedLibrary, StaticLibrary,..., Environment. You can use these to specify your building steps.

scons creates this environment for SConscript files, then forwards the SConscript to the python interpreter. The calls in the SConscript file create a node graphs, which scons uses to do the actual building afterwards.

The src directory

├── src
│   ...
│   └── engine
│       ...
│       └── SCons
│           ├── Action.py Builder.py CacheDir.py Environment.py Executor.py Job.py
│           │   Memoize.py PathList.py SConf.py SConsign.py Util.py Subst.py Warnings.py
│           ├── Node
│           │   ├ ...
│           ├── Options
│           │   ├ ...
│           ├── Platform
│           │   ├── ...
│           ├── Scanner
│           │   ├── C.py
│           │   ├── ...
│           ├── Script
│           │   ├── Main.py SConscript.py
│           │   ├ ...
│           ├── Tool
│           │   ├ ...
│           ├── Variables
│           │   ├── ...
├── test
│   ├── ..

Environment, Action, Builder, ... Node, ... Options, ... Scanner, ... provide the tools to use in the SConscript.

Script contains the scons code that orchestrates everything, the command line parsing, the creation of the environment for SConscript, the building, ... It starts in Script/Main.py.

Creation of SConscript Environment

I'll squash the code and comment it to convey an idea about what is going on. Don't skip these code squashes.

We start in Main.py.

main
    #parse the command line
    parser = SConsOptions.Parser(version)
    values = SConsOptions.SConsValues(parser.get_default_values())
    _exec_main(parser, values) #function
        _main(parser) #function
            # get the SConscript file(s)
            sfile = _SConstruct_exists(repositories=options.repository,
            for script in scripts:
                # interpret SConscript files. further detail below
                SCons.Script._SConscript._SConscript(fs, script)
            #then build
            nodes = _build_targets(fs, options, targets, target_top)
                #in _build_targets
                #targets can come from the command line or
                targets = SCons.Script.BUILD_TARGETS
                #or the default target
                targets = SCons.Script._Get_Default_Targets(d, fs)
                #make nodes
                nodes = [_f for _f in map(Entry, targets) if _f]
                #and then run the required build tasks
                taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace)
                jobs = SCons.Job.Jobs(num_jobs, taskmaster)
                jobs.run(postfunc = jobs_postfunc)

_SConscript is where

  • the globals for the SConscript get provided
  • the SConscript files get interpreted by python

_SConscript is also called by the SConscript command placed in a SConscript file.

_SConscript
    for fn in files:#SConscript files
        call_stack.append(Frame(fs, exports, fn))
            #in Frame
            BuildDefaultGlobals()
                global GlobalDict
                d = SCons.Script.__dict__
                for m in filter(not_a_module, dir(SCons.Script)):
                     GlobalDict[m] = d[m]
        exec _file_ in call_stack[-1].globals

This means that the globals available to a SConscript are in:

src/engine/SCons/Script/__init__.py

Functions of a global default SConsEnvironment instance are made global as well.

SCons.Environment.Environment = SConsEnvironment
DefaultEnvironmentCall
    global _default_env
    _default_env = SCons.Environment.Environment(*args, **kw)

#in Script/__init__.py
for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders:
    exec "%s = _SConscript.DefaultEnvironmentCall(%s)" % (name, repr(name))

Here is the list of the globals (Script/__init__.py).

BuildTask CleanTask QuestionTask AddOption GetOption SetOption Progress GetBuildFailures call_stack Action AddMethod AllowSubstExceptions Builder Configure Environment FindPathDirs Platform Return Scanner Tool WhereIs BoolVariable EnumVariable ListVariable PackageVariable PathVariable Chmod Copy Delete Mkdir Move Touch CScanner DScanner DirScanner ProgramScanner SourceFileScanner CScan DefaultEnvironment ARGUMENTS ARGLIST BUILD_TARGETS COMMAND_LINE_TARGETS DEFAULT_TARGETS Variables Options Command

and the default Environment functions made global (Environment.py, SConscipt.py).

SConscript Default EnsurePythonVersion EnsureSConsVersion Exit Export GetLaunchDir Help Import SConscriptChdir AddPostAction AddPreAction Alias AlwaysBuild BuildDir CacheDir Clean Decider Depends Dir NoClean NoCache Entry Execute File FindFile FindInstalledFiles FindSourceFiles Flatten GetBuildPath Glob Ignore Install InstallAs Literal Local ParseDepends Precious Repository Requires SConsignFile SideEffect SourceCode SourceSignatures Split Tag TargetSignatures Value VariantDir CFile CXXFile DVI Jar Java JavaH Library M4 MSVSProject Object PCH PDF PostScript Program RES RMIC SharedLibrary SharedObject StaticLibrary StaticObject Tar TypeLibrary Zip Package

The Environment is a pivotal element. All variables, lists and classes are in it and can be addressed using $<variable>, which then will be substituted when needed.

SConsEnvironment(Base(SubstitutionEnvironment))
    #the environment dict starts with
    self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment)
    self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self)
    self._dict['PLATFORM'] = str(platform)
    ...
    #then tools are added
    SCons.Tool.Initializers(self) #Install
    if tools is None:
        tools = self._dict.get('TOOLS', None)
        if tools is None:
            # sets up the default tools in SCons/Tool/__init__.tool_list
            tools = ['default']
    apply_tools(self, tools, toolpath)

Every tool is a module in the Tool folder.

class Tool(object):
        module = self._tool_module()
            #load the tool module
            return imp.load_module(self.name, file, path, desc)
        self.generate = module.generate
    def __call__(self, env, *args, **kw):
        env.Append(TOOLS = [ self.name ])
        #the tool module's generate fills the environment with variables, actions,...
        self.generate(env, *args, **kw)

Because of the default tools in tool_list in SCons/Tool/__init__ the environment gets populated with many construction variables (CCFLAGS, ASFLAGS,...).

System environment variables are not used, unless you choose to do so

import os
env = Environment(ENV = {'PATH' : os.environ['PATH']})

env['ENV'][PATH] is used in env.PrependENVPATH, env.AppendENVPath and env.WhereIs.

Actions, Builders and Scanners

Actions do the work. The action types are created using one function: Action. Depending on the parameters it generates one of these types:

CommandAction
CommandGeneratorAction
FunctionAction
ListAction

The __call__() method takes Target, Source, env.

This example creates tst.txt at the time the SConscript is interpreted.

a=Action('touch tst.txt')
a([],[],Environment)

But actual execution is supposed to be done after the interpretation of SConscript orchestrated by Taskmaster.

A Builder wraps an action. And again there is a global Builder factory function. It either has an action or a generator parameter creating an action. A generator can be a function that yields another function or command(s). A builder can also take an emitter modifying target,source,env.

Calling the builder's __call__() creates nodes. The target nodes get an executor, which will execute the action after the SConscript has been interpreted, if necessary. A target will usually have sources that can be targets themselves. This makes up a nodes graphs. There are more of them. If not all targets should be built, specify the wanted one using Default or via command line. A builder can derive the target from the source.

__call__(self, env, target=None, source=None,...
    _execute(self, env, target, source,...
        #in this targets are derived from sources if needed
        tlist, slist = self._create_nodes(env, target, source)
        if executor is None:
            executor = SCons.Executor.Executor(self.action, env, [],...
        for t in tlist:
            t.cwd = env.fs.getcwd()
            t.builder_set(self)
            t.env_set(env)
            t.add_source(slist)
            t.set_executor(executor)
            t.set_explicit(self.is_explicit)
        return SCons.Node.NodeList(tlist)

Builders are in the BUILDERS Environment variable.

The global function Command creates a builder and executes it. It also takes python functions as actions.

The executor will also scan for implicit dependencies using scanner functions taking node,env,path and returning File nodes. Add new scanners with the global Scanner factory function taking scannerfunc,name,optionalarg,skeys,..., skeys being a list of suffixes. Scanners are collected in the SCANNERS environment variable.

Scons Commands in Vim

Vim with python interpreter integrated can be used to experiment with SCons.

The following injects the SCons globals into the current (Vim) python.

import sys
class global_injector:
    def __init__(self):
        try:
            self.__dict__['builtin'] = sys.modules['__builtin__'].__dict__
        except KeyError:
            self.__dict__['builtin'] = sys.modules['builtins'].__dict__
    def __setattr__(self,name,value):
        self.builtin[name] = value
    def update(self,d):
        self.builtin.update(d)
Global = global_injector()

def gscons():
    from SCons.Script.SConscript import BuildDefaultGlobals
    Global.update(BuildDefaultGlobals())

    from SCons.Defaults import DefaultEnvironment
    env = DefaultEnvironment()

    from SCons.Node.FS import FS
    env.fs = FS()
    env.fs.set_SConstruct_dir(env.fs.getcwd())

gscons()

Command returns a node list. A Node has a build() method.

tsttxt = Command('tst.txt',[],'touch tst.txt')
tsttxt[0].build()

The following will only work once, because SCons caches the file system call results. Repeat gscons();tsttxt=..., then it creates tst.txt again.

from SCons.Taskmaster import Taskmaster
from SCons.Job import Jobs
taskmaster=Taskmaster(tsttxt)
jobs = Jobs(1, taskmaster)
jobs.run()

A SCons Example

My example here uses bottle SimpleTemplate to create a c file for a shared library and a test program for that library.

SConstruct

from bottle import SimpleTemplate

def simple(target,source,env):
    for s,t in zip(source,target):
        st=SimpleTemplate(name=s.name)
        with open(t.path,'w') as f: f.write(st.render())

stpl = lambda fl: Command(fl,[f+'.stpl' for f in fl],simple)
stpl('funi.h funi.c tst.cpp'.split())

SharedLibrary('funi.c')
Program('tst.cpp',LIBS=['funi'],LIBPATH=['.'],RPATH=Literal('\\$$ORIGIN'))

funi.h.stpl

#pragma once
#if defined _WIN32 || defined __CYGWIN__
  #ifdef BUILDING_DLL
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllexport))
    #else
      #define DLL_PUBLIC __declspec(dllexport)
    #endif
  #else
    #ifdef __GNUC__
      #define DLL_PUBLIC __attribute__ ((dllimport))
    #else
      #define DLL_PUBLIC __declspec(dllimport)
    #endif
  #endif
  #define DLL_LOCAL
#else
  #if __GNUC__ >= 4
    #define DLL_PUBLIC __attribute__ ((visibility ("default")))
    #define DLL_LOCAL  __attribute__ ((visibility ("hidden")))
  #else
    #define DLL_PUBLIC
    #define DLL_LOCAL
  #endif
#endif

#ifdef __cplusplus
extern "C"
{
#endif
%for i in range(4):
int func{{i}}(int a);
%end
#ifdef __cplusplus
}
#endif

funi.c.stpl

#include "funi.h"
%for i in range(4):
int func{{i}}(int a)
{
    if (a < {{i}})
        return a;
    return {{i}};
}
%end

tst.cpp.stpl

#include <iostream>
#include "funi.h"
using namespace std;
int main()
{
    %for i in range(4):
        cout << func{{i}}(2) << endl;
    %end
    return 0;
}

A variation of this with variant_dir I've posted as answer to this question.

Comparison with Make

Make despite its flaws < whatswrongwithgnumake >  is the predominant build automation system. Let's look at a simple Makefile.

CC=gcc
CCFLAGS=-Wall
LDFLAGS=
SOURCES=$(wildcard *.c)
OBJECTS=$(SOURCES:.c=.o)
TARGET=puttargethere

SCons knows about most variables and they have default values that work on most platforms. You can replace or add to them like this.

env.Replace(CC='mycc')
env.Append(CCFLAGS='-g')

For the following you would use Default.

all: $(TARGET)

SCons can derive the target name from the source name, and it also knows how to build the target. So there is no need to provide the command.

$(TARGET): $(OBJECTS)
        $(CC) $(LDFLAGS) -o $@ $^

%.o: %.c %.h
        $(CC) $(CCFLAGS) -c $<

can be

o = Object('a.c b.c c.c'.split())
Program('puttargethere',o)

but because of the internal knowledge it's enough to have.

Program('puttargethere','a.c b.c c.c'.split())

Configure

The global Configure method creates a SConfBase instance (see SConf.py), whose methods allow to make checks on the system, like autoconf does.

With this we have the following analogy:

make configure && make && make install
scons configure && scons && scons install