< 返回版块

洛佳 发表于 2021-01-19 21:38

使用Rust的科学计算

文章作者是一位物理学研究生,他往常都在Matlab和Python上做科学计算。作者使用Rust重写了生物膜虚拟程序。为此,作者推出了bacon-sci库。

博客地址

创建私有cargo仓库的最佳方法

Reddit网友给出了自己的讨论。

推文地址

Rust有个小问题,每次做科学计算都会遇到

推文地址

给env_logger 追加 wasm 支持

@hUangjj 12月27号在周末的时候尝试着给 env_logger 添加了 wasm 的支持,欢迎大家提点建议

Pull Request链接

中国移动云Rust语言新硬件分布式数据库社招

数据库内核研发 @中国移动云能力中心 ,是PingCAP 的合作伙伴,中移(苏州)软件技术有限公司。

职位信息概览 职位名称:数据库内核研发 职位类型:社招 薪资范围:35k 起 工作地点:苏州 经验要求:硕士及以上,1~3 年相关工作经验 简历请投递至 talents@tidb.io。求职邮件模板请参考 求职邮件模板

职位详情


来自 日报小组 hUangjj

社区学习交流平台订阅:

评论区

写评论
Neutron3529 2021-01-21 21:14

多谢~

--
👇
c5soft: A single issue with Rust that kills me every time with mathematics (rust repo issue 20671) Ok, so I was going to make this as a blog post like I often see on this sub, but I thought I might as well write it out in a post here. I really just want to bring attention to this issue / get suggestions.

I've used rust a lot over the past few years. It's become by go to language for writing mathematical code (pure mathematics, usually research code). I've found that it strikes the perfect balance in making me productive in development (aka not having to spend long hours in gdb just to find a small error, and instead reading a panic message), while giving me optimized code competitive with, and often faster than, my peers' C and C++ code. An example project that I worked on was this combinatorics project, where I found that creating a python interface to my optimized Rust code was a killer combination that got a lot of use out of my peers who were able to take advantage of its raw speed in a very convenient interface.

But in all of my projects, I've run into the same problem. First, when I want my code to be as generic as possible, I use something like the alga crate, or create my own solution (for example when working with weirder things like polynomial rings). This simplifies my code and makes it very generic! Then, I can conveniently say something like "this function takes in two elements of any ring and outputs an element of the same type", etc. The thing that always kills me is that I can't add / do other operations with references of elements with these type constraints. Let me explain a bit...

I would really like to have a trait that expresses "this type's references can be added together". That's fine, you can do that:

trait RefAdd where for<'a,'b> &'a Self: std::ops::Add<&'b Self, Output=Self> {} The problem is that this trait is unusable. These constraints after the "where" clause don't automatically get inferred, so if you write a function:

fn my_fun<T: RefAdd>(x: &T) -> T { x + x } This doesn't compile. Why? Because when you write T: RefAdd, the compiler complains that you don't know if you can add &T's together. But that's what I want to constrain! So, you have to do:

fn my_fun<T: RefAdd>(x: &T) -> T where for<'a, 'b>: &'a T: std::ops::Add<&'b T, Output=T> { x + x } Which makes the trait useless to me, since it doesn't simplify anything! This has been known since 2015 and it hasn't been changed / fixed / easily worked around. There's an issue on the alga crate repo as well.

So at this point, I have a few options:

Use alga's traits, and require T: Copy. This isn't possible when you're, for example, allowing arbitrary sized integers (as I usually want to) or vectors, for example, and that's too limiting.

Use alga's traits, but just clone() everywhere so you're always working with values instead of references. This is usually the solution I pick, which sucks, because then when I share my code with others who aren't seasoned rust users (as I often do), the first words out of my mouth are always "ignore all the .clone()'s". Also, this causes many extra copies, which have the potential to wreck performance (I've never benchmarked this because it's never been that important in my code)

Use a lot of Copy+Paste on these constraints to get unreadable and unmaintainable code, which honestly defeats the purpose.

Macro magic?? I've always suspected you could use some proc macros on every function to automatically give these constraints, which would certainly be more readable, but I've never managed to figure out a way to make it work right.

Like I said, I think I just want to bring this problem up to see what anybody else has to say about it (am I the only one constantly running into this issue?). Should I spend effort coming up with a proc macro solution that I could implement? Would that just be too annoying to use? I appreciate any feedback

If you can use nightly then there's the trait_alias feature which allows writing something like:

trait RefAdd = for<'a, 'b> &'a Self: std::ops::Add<&'b Self, Output=Self>; Trait aliases are a bit different than normal traits and bounds like this will be implied when you require T: RefAdd.

The definitive solution will probably come when RFC 2089 (implied_bounds) will be implemented. I think the necessary work has already be done in chalk, so you'll probably just have to wait until chalk will be fully integrated in rustc (don't quote me on that though).

Should I spend effort coming up with a proc macro solution that I could implement? Would that just be too annoying to use?

I don't think that will be too hard. You may also have fun while exploring how procedural macros work :)

The trait_alias workaround is much better than all my other workarounds, especially since it doesn't sacrifice readability or speed. I was completely unaware of RFC 2089, but now I have something to keep my eye on, so thanks!

I've had kind of a larger project in the back of my mind for a while (gluing a lot of my old code together into a number theory library), and I think using nightly is good enough that I'll finally start working on it and see where it goes

I can confirm that writing proc macros is really fun

The num crate is worth a look - they do implement some of these things via macros. https://github.com/rust-num/num

edit: See for example https://github.com/rust-num/num-bigint/blob/7562ab24330792817e42b808f60b0cac51ca261a/src/macros.rs

Does this help?

https://stackoverflow.com/questions/59520619/how-do-i-specify-a-generic-trait-for-operations-on-references-to-types

You still need the where everywhere you use the trait but you only need a simple where clause and can express as many operations as needed in the trait itself.

This is a nice way to work around it, because as mentioned in their post, the complexity of the where clauses is constant. I feel like this is a nice workaround when I'm not needing to expose the relevant traits, and just use them for internal functions. I appreciate all the solutions!

Had the very same issue while writing a maths lib recently... was sufficiently annoyed before giving up... Then looked at some repos, and used very similar macros to do the same.

Thanks for asking the question, found out nice stuff because of you. :)

If you're not using "standard" numerics, e.g.: wrapping them with an external type.

You can implement something like impl Add<&Self> for &Self this gets around the problem, but it can be exhausting to deal with this for a lot of types at once.

Ok, so I was going to make this as a blog post like I often see on this sub, but I thought I might as well write it out in a post here. I really just want to bring attention to this issue / get suggestions.

Side note: this was a smart move. I seldom ever click into people's blog posts. I do, however, read through most text posts like this.

I've been doing something very similar recently - representation theory of algebras over polynomial rings (so the scalars are not Copy). I'd looked at Num for a while, but the trait has some odd assumptions for mathematical work (i.e. that string parsing is important or that % is a well defined concept).

I was happy to go with a set of very ugly where for<'r> &'r R : Mul<&'r R, Output=R> statements even though it made the "library" code ugly, because I didn't have to clone(). The objects I was dealing with stretched into the 100's of MB in size, so cloning was to be avoided at all costs.

This touched on another bugbear of mine, which is the Eye of Sauron problem. Since the objects can be added by reference, one ends up with code like this in the "application" part of the program.

let a : R = foo();
let b : R = bar();
let c : R = baz();

let result : R = &a * &(&b * &c);

I'm fine if the library is ugly - adding polynomials is boring, but since this is the code that I'd show to other's or spend most of my time working on, I'd like a better than 5/9 signal to noise ratio.

In the end I implemented some of the Mul for R just by fn mul(self, other: R) -> R { &self * &other } but this loses the fact that sometimes it is better to add/mul by value (if, for example you already have a structure initialized and you can just "append" to it). I tried setting up a macro to write these things for me, but the where clauses were getting too complicated for my minuscule understanding of macro magic to fix up.

In conclusion - no, you aren't the only one running into these problems in this area and it is refreshing to find other mathematicians using rust. As you say - its a killer language for speed of development and speed of execution but I think it has some way to go before it is 100% for abstract maths (const generics are slowly trickling in at least!). I'd probably go with 3 or 4 (ideally) even though the interface to your code is mostly where statements

Oh, interesting. A few years ago, during my first attempt at learning Rust, I was trying to make a Runge-Kutta implementation. I could not for the life of me find a way to express "function that returns a value that, after being multiplied by a scalar, can be added to type T to produce a type T". It sounds like a very similar issue, where specifying types that are dependent on operator overloads becomes tricky.

This is actually not hard to dow with where bounds. It's not like c++ where it looks at the implementation to determine the bounds, but it works.

I've also hit this issue while writing code generic over prime fields by-reference. I ended up defining traits FieldLike and RefFieldLike and anotating all functions as

fn my_func(x: &Field) where Field: FieldLike, for<'a> &'a Field: RefFieldLike, It would be so much nicer if I could just write Field: FieldLike instead. (And it looks like trait_alias may allow a way)

Fixed formatting.

Hello, Remco_: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

Maybe I'm missing something here, but why can't you just have an add implementation for the T and a separate one for the &T? I would expect the value version to be implementable in terms of the reference version as well, to save on copy-pasting.

So that's well and good for a specific T, but what if you want a trait that said "both T and &T are Add", for example? That's where things break down. Libraries will make that true for their types, but it's hard to make a generic function that accepts types whose references you can add without this type of trait

c5soft 2021-01-20 08:07

If you can use nightly then there's the trait_alias feature which allows writing something like:

trait RefAdd = for<'a, 'b> &'a Self: std::ops::Add<&'b Self, Output=Self>; Trait aliases are a bit different than normal traits and bounds like this will be implied when you require T: RefAdd.

The definitive solution will probably come when RFC 2089 (implied_bounds) will be implemented. I think the necessary work has already be done in chalk, so you'll probably just have to wait until chalk will be fully integrated in rustc (don't quote me on that though).

Should I spend effort coming up with a proc macro solution that I could implement? Would that just be too annoying to use?

I don't think that will be too hard. You may also have fun while exploring how procedural macros work :)

The trait_alias workaround is much better than all my other workarounds, especially since it doesn't sacrifice readability or speed. I was completely unaware of RFC 2089, but now I have something to keep my eye on, so thanks!

I've had kind of a larger project in the back of my mind for a while (gluing a lot of my old code together into a number theory library), and I think using nightly is good enough that I'll finally start working on it and see where it goes

I can confirm that writing proc macros is really fun

The num crate is worth a look - they do implement some of these things via macros. https://github.com/rust-num/num

edit: See for example https://github.com/rust-num/num-bigint/blob/7562ab24330792817e42b808f60b0cac51ca261a/src/macros.rs

Does this help?

https://stackoverflow.com/questions/59520619/how-do-i-specify-a-generic-trait-for-operations-on-references-to-types

You still need the where everywhere you use the trait but you only need a simple where clause and can express as many operations as needed in the trait itself.

This is a nice way to work around it, because as mentioned in their post, the complexity of the where clauses is constant. I feel like this is a nice workaround when I'm not needing to expose the relevant traits, and just use them for internal functions. I appreciate all the solutions!

Had the very same issue while writing a maths lib recently... was sufficiently annoyed before giving up... Then looked at some repos, and used very similar macros to do the same.

Thanks for asking the question, found out nice stuff because of you. :)

If you're not using "standard" numerics, e.g.: wrapping them with an external type.

You can implement something like impl Add<&Self> for &Self this gets around the problem, but it can be exhausting to deal with this for a lot of types at once.

Ok, so I was going to make this as a blog post like I often see on this sub, but I thought I might as well write it out in a post here. I really just want to bring attention to this issue / get suggestions.

Side note: this was a smart move. I seldom ever click into people's blog posts. I do, however, read through most text posts like this.

I've been doing something very similar recently - representation theory of algebras over polynomial rings (so the scalars are not Copy). I'd looked at Num for a while, but the trait has some odd assumptions for mathematical work (i.e. that string parsing is important or that % is a well defined concept).

I was happy to go with a set of very ugly where for<'r> &'r R : Mul<&'r R, Output=R> statements even though it made the "library" code ugly, because I didn't have to clone(). The objects I was dealing with stretched into the 100's of MB in size, so cloning was to be avoided at all costs.

This touched on another bugbear of mine, which is the Eye of Sauron problem. Since the objects can be added by reference, one ends up with code like this in the "application" part of the program.

let a : R = foo();
let b : R = bar();
let c : R = baz();

let result : R = &a * &(&b * &c);

I'm fine if the library is ugly - adding polynomials is boring, but since this is the code that I'd show to other's or spend most of my time working on, I'd like a better than 5/9 signal to noise ratio.

In the end I implemented some of the Mul for R just by fn mul(self, other: R) -> R { &self * &other } but this loses the fact that sometimes it is better to add/mul by value (if, for example you already have a structure initialized and you can just "append" to it). I tried setting up a macro to write these things for me, but the where clauses were getting too complicated for my minuscule understanding of macro magic to fix up.

In conclusion - no, you aren't the only one running into these problems in this area and it is refreshing to find other mathematicians using rust. As you say - its a killer language for speed of development and speed of execution but I think it has some way to go before it is 100% for abstract maths (const generics are slowly trickling in at least!). I'd probably go with 3 or 4 (ideally) even though the interface to your code is mostly where statements

Oh, interesting. A few years ago, during my first attempt at learning Rust, I was trying to make a Runge-Kutta implementation. I could not for the life of me find a way to express "function that returns a value that, after being multiplied by a scalar, can be added to type T to produce a type T". It sounds like a very similar issue, where specifying types that are dependent on operator overloads becomes tricky.

This is actually not hard to dow with where bounds. It's not like c++ where it looks at the implementation to determine the bounds, but it works.

I've also hit this issue while writing code generic over prime fields by-reference. I ended up defining traits FieldLike and RefFieldLike and anotating all functions as

fn my_func(x: &Field) where Field: FieldLike, for<'a> &'a Field: RefFieldLike, It would be so much nicer if I could just write Field: FieldLike instead. (And it looks like trait_alias may allow a way)

Fixed formatting.

Hello, Remco_: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

Maybe I'm missing something here, but why can't you just have an add implementation for the T and a separate one for the &T? I would expect the value version to be implementable in terms of the reference version as well, to save on copy-pasting.

So that's well and good for a specific T, but what if you want a trait that said "both T and &T are Add", for example? That's where things break down. Libraries will make that true for their types, but it's hard to make a generic function that accepts types whose references you can add without this type of trait

c5soft 2021-01-20 08:03

A single issue with Rust that kills me every time with mathematics (rust repo issue 20671) Ok, so I was going to make this as a blog post like I often see on this sub, but I thought I might as well write it out in a post here. I really just want to bring attention to this issue / get suggestions.

I've used rust a lot over the past few years. It's become by go to language for writing mathematical code (pure mathematics, usually research code). I've found that it strikes the perfect balance in making me productive in development (aka not having to spend long hours in gdb just to find a small error, and instead reading a panic message), while giving me optimized code competitive with, and often faster than, my peers' C and C++ code. An example project that I worked on was this combinatorics project, where I found that creating a python interface to my optimized Rust code was a killer combination that got a lot of use out of my peers who were able to take advantage of its raw speed in a very convenient interface.

But in all of my projects, I've run into the same problem. First, when I want my code to be as generic as possible, I use something like the alga crate, or create my own solution (for example when working with weirder things like polynomial rings). This simplifies my code and makes it very generic! Then, I can conveniently say something like "this function takes in two elements of any ring and outputs an element of the same type", etc. The thing that always kills me is that I can't add / do other operations with references of elements with these type constraints. Let me explain a bit...

I would really like to have a trait that expresses "this type's references can be added together". That's fine, you can do that:

trait RefAdd where for<'a,'b> &'a Self: std::ops::Add<&'b Self, Output=Self> {} The problem is that this trait is unusable. These constraints after the "where" clause don't automatically get inferred, so if you write a function:

fn my_fun<T: RefAdd>(x: &T) -> T { x + x } This doesn't compile. Why? Because when you write T: RefAdd, the compiler complains that you don't know if you can add &T's together. But that's what I want to constrain! So, you have to do:

fn my_fun<T: RefAdd>(x: &T) -> T where for<'a, 'b>: &'a T: std::ops::Add<&'b T, Output=T> { x + x } Which makes the trait useless to me, since it doesn't simplify anything! This has been known since 2015 and it hasn't been changed / fixed / easily worked around. There's an issue on the alga crate repo as well.

So at this point, I have a few options:

Use alga's traits, and require T: Copy. This isn't possible when you're, for example, allowing arbitrary sized integers (as I usually want to) or vectors, for example, and that's too limiting.

Use alga's traits, but just clone() everywhere so you're always working with values instead of references. This is usually the solution I pick, which sucks, because then when I share my code with others who aren't seasoned rust users (as I often do), the first words out of my mouth are always "ignore all the .clone()'s". Also, this causes many extra copies, which have the potential to wreck performance (I've never benchmarked this because it's never been that important in my code)

Use a lot of Copy+Paste on these constraints to get unreadable and unmaintainable code, which honestly defeats the purpose.

Macro magic?? I've always suspected you could use some proc macros on every function to automatically give these constraints, which would certainly be more readable, but I've never managed to figure out a way to make it work right.

Like I said, I think I just want to bring this problem up to see what anybody else has to say about it (am I the only one constantly running into this issue?). Should I spend effort coming up with a proc macro solution that I could implement? Would that just be too annoying to use? I appreciate any feedback

Neutron3529 2021-01-19 22:10

Rust有个小问题,每次做科学计算都会遇到

能放一个简介吗?

无力翻墙,看不见,眼馋~

1 共 4 条评论, 1 页