I am sure you have heard about Wolfram Alpha. Some of you would have used its Mathematics section at some point of time to cross check your solution to a maths problem. If you haven’t, please do. A computer algebra system (CAS) does the same thing. Algebraic computation or symbolic computation are all used interchangeably. A CAS solves problems the same way a human does, but way more quickly and precisely. There is very less chance for an error, that we humans often make.
My project was to write the Ruby extensions for the library SymEngine and come up with a Ruby-ish interface, after which we can use the features of SymEngine from Ruby.
SymEngine is a library for symbolic computation in C++. You may ask, why SymEngine? There are other CASs that I know of. This question was indeed asked. At the beginning, the idea was to use ruby wrappers for sage (a mathematics software system) which uses Pynac, an interface to GiNaC (another CAS). As it turns out from the benchmarks, SymEngine is much faster than Pynac. What about directly wrapping GiNaC? SymEngine is also a bit faster than GiNaC.
The motivation for SymEngine itself is to develop it once in C++ and then use it from other languages rather than doing the same thing all over again for each language that it is required in. In particular, a long term goal is to make Sage as well as SymPy use it by default, thus unifying the Python CAS communities. The goal of implementing the Ruby wrappers is to provide a CAS for the Ruby community.
How will this be useful?
There are times when we might need a symbolic computation library. Here is an incomplete list of some of the situations:
- We need to do some algebra to get systems of equations in suitable form for numerical computation.
- We need to make substitutions for some variables and don’t want to risk a math error by hand. There might be times when we want to partially simplify an expression by substituting only a few of its variables.
- We have a situation where we need to find the optimum number of something to maximise our profits, and the mathematical model devised is just too complicated to solve by hand.
- We need to perform non-trivial derivatives or integrals.
- We are trying to understand the domain of a new function.
- Most root finding algorithms search for a single root, whereas often there are more than one.
- We need to solve a linear system or manipulate a symbolic matrix exactly.
- We need to manipulate exact expressions and solutions, as opposed to approximate ones using a numerical method.
With that said, a symbolic manipulation library is indispensable for scientists and students. Ruby has gained a great deal of popularity over the years, and a symbolic manipulation library gem like this project in Ruby might prove to be the foundation for a computer algebra system in Ruby. With many efforts like these, Ruby might become the first choice for academicians given how easy it is to code your logic in Ruby.
How to install the gem?
To install, please follow the compile instructions given in the README. After you are done, I would suggest to test the extensions. To run the test suite execute
rspec spec on the command line, from the
The gem is still in alpha release. Please help us out by reporting any issues in the repo issue tracker.
What can I do with the gem?
Currently, the following features are available in the gem:
- Construct expressions out of variables (mathematical).
- Simplify the expressions.
- Carry out arithmetic operations like
** with the variables and expressions.
- Extract arguments or variables from an expression.
- Differentiate an expression with respect to another.
- Substitute variables with other expressions.
Features that will soon be ported to the SymEngine gem - Functions, including trigonometric, hyperbolic and some special functions. - Matrices, and their operations. - Basic number-theoretic functions.
I have developed a few IRuby notebooks that demonstrate the use of the new SymEngine module in ruby.
Below is an example taken from the notebooks.
Using the SymEngine Gem
SymEngine is a module in the extensions, and the classes are a part of it. So first you fire up the interpreter or an IRuby notebook and load the file:
Go ahead and try a function:
1 2 3 4 5 6
or create a variable:
This shows that we have successfully loaded the module.
Just like there are variables like x, y, and z in a mathematical expression or equation, we have
SymEngine::Symbol in SymEngine to represent them. To use a variable, first we need to make a
SymEngine::Symbol object with the string we are going to represent the variable with.:
1 2 3 4 5 6 7
Then we can construct expressions out of them:
1 2 3
In SymEngine, every object is an instance of Basic or its subclasses. So, even an instance of
SymEngine::Symbol is a Basic object.:
1 2 3 4 5
Now that we have an expression, we would like to see it’s expanded form using
1 2 3
Or check if two expressions are same:
f are not equal since they are only mathematically equal, not structurally:
Let us suppose you want to know what variables/symbols your expression has. You can do that with the
#free_symbols method, which returns a set of the symbols that are in the expression.:
Let us use
#map method to see the elements of the
#args returns the terms of the expression,:
or if it is a single term it breaks down the elements:
You can make objects of class
SymEngine::Integer. It’s like regular
Integer in ruby kernel, except it can do all the operations a
Basic object can — such as arithmetic operations, etc.:
1 2 3 4 5
Additionally, it can support numbers of arbitrarily large length.
You can also make objects of class
SymEngine::Rational which is the SymEngine counterpart for
Rationals in Ruby.:
1 2 3 4
Like any other
Basic object arithmetic operations can be done on this rational type too.:
You need not create an instance of
SymEngine::Rational, every time you want to use them in an expression that uses many
Integers. Let us say you already have
Rational object. Even then you can use them without having to create a new
1 2 3
As you can see, ruby kernel
Rationals interoperate seamlessly with the
What I learned
In the rest of the post, I would like to summarise my work and what I learned as a participant of Google Summer of Code 2015.
I am a newbie when it comes to Ruby, and it took me a while to setup the gem and configure files for the building of extensions.
The struggle between shared, static and dynamic libraries
I faced a lot of problem in the early stages, when I was trying to build the extensions. Ondrej, my mentor, and Isuru, a fellow GSoC student, helped me a lot. There were many C flags that were being reported as missing. Some flags
cmake added by default but
extconf.rb didn’t, the same one that was required to be added to build it as a shared library. I am still confused about the details, some of which are explored in greater detail in my personal blog. Finally, the library had to be built as a dynamic one. The problem of missing C flags was resolved later by hooking the process to
cmake rather than
Load Errors and problems in linking
One of our aims during developing this was to get rid of unessential dependencies. The ones we already had the tools for. Like later the file
extconf.rb, that is used to generate Makefile for the extension was removed, because that could also be done by
cmake. Flags were added to
cmake for building the Ruby extensions, like the flag
Makefile then generates the library
symengine.so in the directory
extconf.rb, the file
extconf.h was also gone. Along these lines, the dependency on
rake was also removed, and with that the
Rakefile. Any task automation will most probably be done in python. So, the
Rake::ExtensionTask was done by
cmake and the
Rake::GemPackageTask was replaced by the manual method of
gem build symengine.gemspec and
gem install symengine-0.0.0.gem
Not many projects have travis-ci setup for multiple languages. Not even the tutorials had clearly mentioned about setting up for multiple languages. But I did know about one of them, which is Shogun, the machine-learning toolbox. I referred to their
.travis.yml and setup it up. If something like this wouldn’t have worked the plan was to manually install the required version of ruby and then execute the shell commands.
Making a basic object
Finally, I was able to successfully build the extensions, link the extensions with the SymEngine library, load the ruby-extension library in the interpreter and successfully instantiate an object of type
Inheritance and Symbols
At this time, the way inheritance works(like the sequence of formation and destruction of objects of a class that had a superclass) with the Ruby C API, was confusing for all of us. I designed an experiment
to check what was actually happening. That cleared things out, and made the it easier to wrap things from now on. I also wrapped the
Symbol class during the course.
Redesign of the C interface
We had to design an ugly function to wrap vector in C. That led us to redesign the C interface. This approach had no reinterpret casting that was being done earlier. Each data structure had a type that was determined at compile time. For C, it was an opaque structure, while for C++ the opaque structure declared in the shared header file was implemented in the source file that had C++ data types. This blog post explains it further.
Integer and Rational
While trying to port the SymEngine classes,
Rational, I had to port many methods in
Basic before that. I also replicated the
rake tasks in NMatrix, for detection of memory leaks, in form of bash scripts.
Since all objects in the Ruby C API are of the type
CBasic, we needed a function that would give us the typename during the runtime for the corresponding objects to be wrapped in ruby, as an object of the correct
Class in ruby. Since, this was achieved with
enum in C++, the same thing could be done in C too, with all the classes written manually again. But there was no guarantee for this to be consistent, if ever the features required to be wrapped for a new language, and also manually adding the class in all the enum list everytime a new class is added was prone to errors. So, to make this DRY, we automated this by sharing the list of enums. More details for the implementation can be found here.
Class coercion and interoperability
To support interoperability with the builtin ruby types, I had to overload the methods in builtin classes earlier(this was not continued). Overriding all the existing binary operations for a ruby class to support SymEngine types, violated the open/closed principle. There was indeed another way, which is ‘Class Coercion’. It was suggested by Isuru. After that, SymEngine types could seamlessly interoperate between the ruby types.
After this, all the arithmetic operations had been successfully ported. Each
Basic object can now perform arithmetic operations with other
Basic object(sometimes even ruby objects like
Integer). The test file in python, that had all the corresponding test cases was ported to its RSpec counterpart.
Recently I completed porting the substitutions module to the extensions(
#subs). This feature has added a lot of convenience as now you can substitute a
SymEngine::Symbol with some other value in an expression and then
#expand to get the result.
Currently, I am working on porting the trigonometric functions in SymEngine to the extensions. This would first require to wrap the
Function class and then the
TrigFunction class in SymEngine.
Integration of other Ruby gems
I also have plans to integrate the ruby bindings for
mpc libraries, that are already available as gems, with ruby bindings for our library. I have created an issue here. Feel free to drop any suggestions.
There is much scope for improvement in both the projects. For SymEngine, to support more features like polynomials and series-expansion in the near future, and improving the user interface and the exception handling for the extensions. In short, making the extensions more ruby-ish.
I am grateful to my mentor, Mr. Ondřej Čertík, the Ruby Science Foundation and the SymPy Organisation for the opportunity that they gave me and guiding me through the project, and my team-mates for helping me with the issues. I hope more people will contribute to the project and together we will give a nice symbolic manipulation gem to the Ruby community.