Can you create a pull request for the ode45 patch? Apart from translating the code from octave to julia, I have not done much more with it at this point. Perhaps we should move the ode stuff into examples for now, and let it bake there before it is moved back into jl/.
I'm not sure I fully understand the performance bit. Are you saying that the ODE solver has poor performance, or is it the AD code that performs slower than you expect? If both x and d are vectors, you could interleave them by storing into an Nx2 Matrix. The performance of an Array of user defined types is not going to be very good right now - otherwise that would have been the way to go.
The general discussion about style and performance can probably happen on the list. But, issues related to AD could be filed, and a basic design could be created on the wiki. Your code can go into examples/ for now. Would also love to hear what the folks working on ODE stuff have to say on the topic and how to push forward.
-viral
> <ode.patch><ad.jl><ad_test.jl>
On a similar note: Is there an elegant way to give something a new name or subtype it, so that functions can be specialized differently?
For example (useless, just an illustration), if I wanted to have a different Rational type, that uses the same kind of storage but acts differently, I would love to be able to do something like
MyRational{T}<:Rational{T}
+(x::MyRational, y::MyRational) = ...
This kind of subtyping is impossible, at least for parameterized types, correct?
-viral
This could actually be one of the reasons why many of the other scientific computing languages have all the flaws they do: Some mathematician, physicist, engineer, etc. feels he needs to solve a problem that none of the established languages is well suited for and decides to make their own. Chaos ensues. Even with my limited understanding of CS, looking at the internals of R, for example, is horrifying (everything breaks if you try to link a multi-threaded library, etc.).
Coming back to the original question and pushing the idea a little further, it seems natural to me to always have an abstract type, which contains the semantics, and a concrete type that realizes it. However, this is probably why there are a lot of C++ libraries that are cluttered with interfaces everywhere, and most of them end up having only one implementation anyway :-) So I guess it is a trade-off between generality and pragmatism. Would it make sense to associate an abstract type to every concrete type and make a distinction in the methods? This would of course only make sense if there is some kind of canonical implementation to the idea. For example
type NumericalMatrix
data::Array{Float64, 2}
end
ref(X::NumericalMatrix, i, j)=X.data[i,j]
@abstract trace(X::NumericalMatrix)=begin
s=0.0
n=arraysize(X.data)[1]
m=arraysize(X.data)[2]
if n!=m
error("not square")
end
for i=1:n
s+=ref(X,i,i)
end
s
end
Now one could do sth. like
type SparseMatrix<:AbstractType{NumericalMatrix}
...
end
ref(X::SparseMatrix, i.j) = ...
and keep the trace functionality of the other class. Sorry for coming up with so many (probably stupid) questions and examples :)
type A <: B# stuffend
A <: Abstract{A} <: B
C <: Abstract{C} <: D
PS: Please don't change the Rational type (unless someone else needs that)! Like I said, I have no intentions of extending it, I was just curious about how one would do something like this in general.
Another problem is that it doesn't really give you what you want in a lot of cases. If you declare v::Vector{A}, then you can't put B object into that vector. You'd have to declare it to be v::Vector{Abstract{A}}, in which case you can't have compact C-style array storage.
If you declare v::Vector{B}, you can't put a A object into v; you have to declare it as v::Vector{Abstract{B}}.
you would rather have:A <: Abstract{A} <: B
A <: Abstract{A} <: Abstract{B}
B <: Abstract{B}
a = Array(AbstractA,10)
f(a)
...
We don't know in general whether f will only store "A"s into the
array. Yes, in some cases you could see all uses and represent the
array more efficiently with that knowledge. I suspect those cases
would be rare for us.
You could also combine run-time techniques, such as switching the
representation if something other than an "A" is stored. But then
you'd be in a situation where looking at the type of an array doesn't
tell you its representation, so there would be some unavoidable
overhead.
> Hope this didn't come off as too ranty. Just wanted to attempt to explain and justify
> this fairly significant design decision.
On the contrary, I suggest to add explanations along these lines to the documentation,
as the question will likely come up again.
> Inheriting common fields is a weird bogus way of saving
> keystrokes at the cost of tightly coupling two implementations
> to each other. All the examples I've ever seen where someone
> tacks a few fields onto another type are pretty contrived
> (think chapter three of an intro to some oo language book).
I can provide a few real-life examples if you are interested but I
agree that they are not nearly as important or frequent as OO
textbooks tend to suggest, and that they are not worth the trouble
that the most popular solution (allowing type extensions with new
fields) produce.
The day I have to tackle such a situation in Julia, I'll probably make
a macro that defines the fields of the base type plus additional types
provided as an argument to the macro. Then I can define as many
extended types as I want and still modify their common fields in a
single place if needed. That macro could even add "convert" methods to
do the equivalent of downcasting.
However, the original question was about a somewhat different situation:
> On Wednesday, March 14, 2012 2:19:45 PM UTC-5, jrauch wrote:
>
> On a similar note: Is there an elegant way to give something
> a new name or subtype it, so that functions can be
> specialized differently? For example (useless, just an
> illustration), if I wanted to have a different Rational
> type, that uses the same kind of storage but acts
> differently, I would love to be able to do something like
What he seems to ask for is a typealias that creates a distinct type
(not a subtype) which has different behavior from the original type
but shares its implementation.
I see this type of construction all the time in Haskell code, where
types are the universal weapon for everything, including program
structure. In Haskell, people create a new algebraic data type with a
single field holding the value of the original type they want to
wrap. They also define the required conversion functions to work with
the original type through the interface of the new one. In Haskell,
one can rely on the compiler to optimize away the wrappers, so this is
an acceptable solution performance-wise, even though it clutters the
code significantly.
I haven't used Julia enough to say if there is a need for some such
construction, nor whether the Haskell approach would be efficient.
One potential application that has been recently discussed here is the
different views of arrays. With different types that share a common
implementation, one could have Array as an APL-style container with
elementwise operations, Matrix for a Matlab-style interface, and Frame
for statisticians coming from R.
Konrad.
And as Stefan said, Complex numbers were defined this way --- Complex
is abstract, and there are Complex128, Complex64, and ComplexPair
concrete implementations. All complex arithmetic is defined on
Complex, so those functions work on all concrete complex numbers.
Is this what you're referring to?
@abstract store(a::Vector{Abstract{B}}, x::Abstract{B}, i)
> I believe abstract types handle this --- for example we have many
> functions defined on AbstractArray, which are likely to work for
> anything array-like.
The problem with using abstract types here is that you need to modify
the subtype graph, which also means modifying the implementation of
the original type.
To stick to the original somewhat contrived example: suppose I want to
provide alternate behavior for type Rational. That type is part of the
Julia standard library, so I'd have to introduce a new abstract
type above Rational into the standard library. That's not a workable
approach in practice.
BTW, I wonder how typealias works and what is good for. I didn't yet find
any difference in result between
typealias Foo Int32
and
Foo = Int32
Is there one?
Konrad.
BTW, I wonder how typealias works and what is good for. I didn't yet find
any difference in result between
typealias Foo Int32
and
Foo = Int32
Is there one?
Konrad.
typealias Foo Int32
const Foo = Int32
typealias Vector{T} Array{T,1}
Regarding the proposal of automatically sticking abstract types "above" every concrete type, some kind of implementation like this might be possible. It seems like the proposal boils down to automatically sticking an abstract A' just above every concrete type A and then actually using abstract A' everywhere A is used for dispatch; for other uses of A, like in type parameters for arrays, A just means A. Something like that could probably be made to work (it's reminiscent of how singleton classes work in Ruby).However, I'm going to argue against having this ability from a completely social standpoint. Since Julia is dynamic and open source, if you need an abstract supertype of some concrete type, you can always just patch your copy of the definition of Rational and explicitly stick an abstract supertype in there. When this happens, for code maintenance sanity, you're going to want to push that change upstream to whoever maintains the official version of the Rational type. When you do that, you start a conversation with that person about why you feel that Rational needs a supertype. They can either persuade you that it's a bad idea and provide a better solution, or you can persuade them that it is a good idea and get them to change the official version. In the latter case, the official Rational really needed an abstract supertype all along, and now it gets one. Everyone benefits! Regardless of how the conversation goes, the fact that the conversation happened is a good thing.Moreover, the Rational code most probably ought to be refactored to some extent to be used as an abstraction. Maybe you can use it that way even when that wasn't intended, but it's probably going to be suboptimal and maybe even ugly. For example, significant refactoring was needed when we changed Complex from a single concrete implementation into an abstraction with three implementations (ComplexPair, Complex64, and Complex128). If anyone can just jack a new implementation under something that was not intended to be an abstraction, then there's a huge temptation to just do that instead of trying to get the official version patched. The conversation with the maintainer never happens, the patch never gets submitted, and the next person who needs to use Rational as an abstraction ends up also doing something suboptimal that kind of works but wasn't intended.I think the idea that you need to inherit from types that may or may not have been intended to be inherited from is an example of "closed-source thinking" and only makes sense for statically compiled languages. That ability is essential for a language like Java where the developer may not have access to the source code of a class they're extending and thus may not be able to change its behavior. In a dynamic, open source language like Julia, where you always have access to the source and can change anything, this is not necessary. Furthermore, I would argue that it is detrimental to the language ecosystem as a whole if people aren't strongly "encouraged" to push back changes to maintainers when some type doesn't anticipate some need.
> However, I'm going to argue against having this ability from a
> completely social standpoint. Since Julia is dynamic and open
> source, if you need an abstract supertype of some concrete
> type, you can always just patch your copy of the definition of
> Rational and explicitly stick an abstract supertype in there. When
That's an interesting argument, though I don't believe it will work
before I see it working. Scientists are sceptics by nature or by
training ;-)
What you are advocating is that everyone using Julia should work on
the bleeding edge, pulling in upstream changes regularly in order to
be able to use the latest features, in particular those that the user
has asked for himself. I don't quite see this happening any time soon,
judging from the prehistoric versions of many programs I see on my
colleagues' computers. Most computational scientists are very
reluctant to change anything in their work environment, partly out of
laziness, but partly out of the justified fear that some update will
break their code. There are also institutional barriers to updates,
in particular in high-security environments such as defense.
In the long run, what you describe could work out and in fact I hope it
will. I just wonder if it will happen before my retirement.
One ingredient missing in today's software development toolchain is an
integrated version control and dependency management system. I should
be able to say that my program X depends on a specific version of
Julia (referenced by the commit number). That way I could be sure that
working code remains working, even if pull in updates and use the new
features in other projects. The Nix package manager
(http://nixos.org/nix/) works along these principles, but is not yet
solid enough for daily use.
The other ingredient that would have to change is the level of
sophistication of scientists-programmers. Most of my colleagues just
begin to see the point of version control, but don't use it yet for
their daily work.
Konrad.
> Regarding package management, I completely agree that integration
> with version control is a great idea. I've proposed this myself
> in issue #432 and elsewhere (some email thread?). I favor tight
> integration with git, since it has become the de facto open source
Fine, as long as mere users of some library don't need to learn git
first.
> version control standard and it's what we use. I also had the idea
> of versioning snapshots of collections of packages by making the
> package directory a git repo with installed packages as
> submodules. That way entire collections of packages can be tracked
> using git and a single git version number tells you all of the
> package versions, and you can get them all on a new system with a
That sounds very nice, but it requires power-user knowledge of git.
Of course some more user-friendly package manager could be built
on top of raw git and make all that goodness more accessible.
My personal long-term vision for integrated version control and dependency
management goes even further to include scientific data, mixed-language
software implementations, and scientific publishing:
http://dirac.cnrs-orleans.fr/plone/software/activepapers
Unfortunately my proof-of-concept implementation is based on the JVM,
which pretty much no one uses in computational science. I hope to find
the time to explore other possible bases, e.g. LLVM.
Konrad.
My personal long-term vision for integrated version control and dependency
management goes even further to include scientific data, mixed-language
software implementations, and scientific publishing:
http://dirac.cnrs-orleans.fr/plone/software/activepapers
Unfortunately my proof-of-concept implementation is based on the JVM,
which pretty much no one uses in computational science. I hope to find
the time to explore other possible bases, e.g. LLVM.
Konrad.
> That's very cool. I had similar ambitions, although my thoughts on the subject are
> generally more along the lines of "github for science": a service where you can access
> both the data and the code that were used in the same place, and replicate results —
> possibly on virtualized hardware, so that an identical experimental setting can be
> relatively easily reproduced.
That sounds a bit similar to SHARE:
http://is.ieis.tue.nl/staff/pvgorp/share/
The main difference is that SHARE doesn't have anything similar to a Github fork,
meaning it's difficult to re-use code and data from others in a different context.
> Of course, for both ideas, one of the hardest problems is buy in:
> how do you get people to use it?
By making it easy to use, and integrate it into the tools that
scientists are already familiar with. At least that's my theory ;-)
Konrad.