Merge pull request #886 from Andlon/matrix-macro

Matrix macro
This commit is contained in:
Sébastien Crozet 2021-05-09 16:52:04 +02:00 committed by GitHub
commit 23ac85e896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 670 additions and 7 deletions

View File

@ -69,6 +69,12 @@ jobs:
- name: test nalgebra-sparse (slow tests)
# Unfortunately, the "slow-tests" take so much time that we need to run them with --release
run: PROPTEST_CASES=10000 cargo test --release --manifest-path=nalgebra-sparse/Cargo.toml --features compare,proptest-support,slow-tests slow
test-nalgebra-macros:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: test nalgebra-macros
run: cargo test -p nalgebra-macros
build-wasm:
runs-on: ubuntu-latest
# env:

View File

@ -22,7 +22,7 @@ name = "nalgebra"
path = "src/lib.rs"
[features]
default = [ "std" ]
default = [ "std", "macros" ]
std = [ "matrixmultiply", "simba/std" ]
sparse = [ ]
debug = [ "approx/num-complex", "rand" ]
@ -32,6 +32,7 @@ compare = [ "matrixcompare-core" ]
libm = [ "simba/libm" ]
libm-force = [ "simba/libm_force" ]
no_unsound_assume_init = [ ]
macros = [ "nalgebra-macros" ]
# Conversion
convert-mint = [ "mint" ]
@ -60,6 +61,7 @@ proptest-support = [ "proptest" ]
slow-tests = []
[dependencies]
nalgebra-macros = { version = "0.1", path = "nalgebra-macros", optional = true }
typenum = "1.12"
rand-package = { package = "rand", version = "0.8", optional = true, default-features = false }
num-traits = { version = "0.2", default-features = false }
@ -92,7 +94,7 @@ matrixcompare = "0.3.0"
itertools = "0.10"
[workspace]
members = [ "nalgebra-lapack", "nalgebra-glm", "nalgebra-sparse" ]
members = [ "nalgebra-lapack", "nalgebra-glm", "nalgebra-sparse", "nalgebra-macros" ]
resolver = "2"
[[example]]
@ -109,4 +111,4 @@ lto = true
[package.metadata.docs.rs]
# Enable certain features when building docs for docs.rs
features = [ "proptest-support", "compare" ]
features = [ "proptest-support", "compare", "macros" ]

View File

@ -0,0 +1,25 @@
[package]
name = "nalgebra-macros"
version = "0.1.0"
authors = [ "Andreas Longva", "Sébastien Crozet <developer@crozet.re>" ]
edition = "2018"
description = "Procedural macros for nalgebra"
documentation = "https://www.nalgebra.org/docs"
homepage = "https://nalgebra.org"
repository = "https://github.com/dimforge/nalgebra"
readme = "../README.md"
categories = [ "science", "mathematics" ]
keywords = [ "linear", "algebra", "matrix", "vector", "math" ]
license = "Apache-2.0"
[lib]
proc-macro = true
[dependencies]
syn = { version="1.0", features = ["full"] }
quote = "1.0"
proc-macro2 = "1.0"
[dev-dependencies]
nalgebra = { version = "0.26.1", path = ".." }
trybuild = "1.0.42"

282
nalgebra-macros/src/lib.rs Normal file
View File

@ -0,0 +1,282 @@
//! Macros for `nalgebra`.
//!
//! This crate is not intended for direct consumption. Instead, the macros are re-exported by
//! `nalgebra` if the `macros` feature is enabled (enabled by default).
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::Expr;
use syn::{parse_macro_input, Token};
use proc_macro2::{Delimiter, Spacing, TokenStream as TokenStream2, TokenTree};
use proc_macro2::{Group, Punct};
struct Matrix {
// Represent the matrix as a row-major vector of vectors of expressions
rows: Vec<Vec<Expr>>,
ncols: usize,
}
impl Matrix {
fn nrows(&self) -> usize {
self.rows.len()
}
fn ncols(&self) -> usize {
self.ncols
}
/// Produces a stream of tokens representing this matrix as a column-major nested array.
fn to_col_major_nested_array_tokens(&self) -> TokenStream2 {
let mut result = TokenStream2::new();
for j in 0..self.ncols() {
let mut col = TokenStream2::new();
let col_iter = (0..self.nrows()).map(move |i| &self.rows[i][j]);
col.append_separated(col_iter, Punct::new(',', Spacing::Alone));
result.append(Group::new(Delimiter::Bracket, col));
result.append(Punct::new(',', Spacing::Alone));
}
TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, result)))
}
/// Produces a stream of tokens representing this matrix as a column-major flat array
/// (suitable for representing e.g. a `DMatrix`).
fn to_col_major_flat_array_tokens(&self) -> TokenStream2 {
let mut data = TokenStream2::new();
for j in 0..self.ncols() {
for i in 0..self.nrows() {
self.rows[i][j].to_tokens(&mut data);
data.append(Punct::new(',', Spacing::Alone));
}
}
TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, data)))
}
}
type MatrixRowSyntax = Punctuated<Expr, Token![,]>;
impl Parse for Matrix {
fn parse(input: ParseStream) -> Result<Self> {
let mut rows = Vec::new();
let mut ncols = None;
while !input.is_empty() {
let row_span = input.span();
let row = MatrixRowSyntax::parse_separated_nonempty(input)?;
if let Some(ncols) = ncols {
if row.len() != ncols {
let row_idx = rows.len();
let error_msg = format!(
"Unexpected number of entries in row {}. Expected {}, found {} entries.",
row_idx,
ncols,
row.len()
);
return Err(Error::new(row_span, error_msg));
}
} else {
ncols = Some(row.len());
}
rows.push(row.into_iter().collect());
// We've just read a row, so if there are more tokens, there must be a semi-colon,
// otherwise the input is malformed
if !input.is_empty() {
input.parse::<Token![;]>()?;
}
}
Ok(Self {
rows,
ncols: ncols.unwrap_or(0),
})
}
}
/// Construct a fixed-size matrix directly from data.
///
/// **Note: Requires the `macro` feature to be enabled (enabled by default)**.
///
/// This macro facilitates easy construction of matrices when the entries of the matrix are known
/// (either as constants or expressions). This macro produces an instance of `SMatrix`. This means
/// that the data of the matrix is stored on the stack, and its dimensions are fixed at
/// compile-time. If you want to construct a dynamic matrix, use [`dmatrix!`] instead.
///
/// `matrix!` is intended to be both the simplest and most efficient way to construct (small)
/// matrices, and can also be used in *const fn* contexts.
///
/// The syntax is MATLAB-like. Column elements are separated by a comma (`,`), and a semi-colon
/// (`;`) designates that a new row begins.
///
/// # Examples
///
/// ```
/// use nalgebra::matrix;
///
/// // Produces a Matrix3<_> == SMatrix<_, 3, 3>
/// let a = matrix![1, 2, 3;
/// 4, 5, 6;
/// 7, 8, 9];
/// ```
///
/// You can construct matrices with arbitrary expressions for its elements:
///
/// ```
/// use nalgebra::{matrix, Matrix2};
/// let theta = 0.45f64;
///
/// let r = matrix![theta.cos(), - theta.sin();
/// theta.sin(), theta.cos()];
/// ```
#[proc_macro]
pub fn matrix(stream: TokenStream) -> TokenStream {
let matrix = parse_macro_input!(stream as Matrix);
let row_dim = matrix.nrows();
let col_dim = matrix.ncols();
let array_tokens = matrix.to_col_major_nested_array_tokens();
// TODO: Use quote_spanned instead??
let output = quote! {
nalgebra::SMatrix::<_, #row_dim, #col_dim>
::from_array_storage(nalgebra::ArrayStorage(#array_tokens))
};
proc_macro::TokenStream::from(output)
}
/// Construct a dynamic matrix directly from data.
///
/// **Note: Requires the `macro` feature to be enabled (enabled by default)**.
///
/// The syntax is exactly the same as for [`matrix!`], but instead of producing instances of
/// `SMatrix`, it produces instances of `DMatrix`. At the moment it is not usable
/// in `const fn` contexts.
///
/// ```
/// use nalgebra::dmatrix;
///
/// // Produces a DMatrix<_>
/// let a = dmatrix![1, 2, 3;
/// 4, 5, 6;
/// 7, 8, 9];
/// ```
#[proc_macro]
pub fn dmatrix(stream: TokenStream) -> TokenStream {
let matrix = parse_macro_input!(stream as Matrix);
let row_dim = matrix.nrows();
let col_dim = matrix.ncols();
let array_tokens = matrix.to_col_major_flat_array_tokens();
// TODO: Use quote_spanned instead??
let output = quote! {
nalgebra::DMatrix::<_>
::from_vec_storage(nalgebra::VecStorage::new(
nalgebra::Dynamic::new(#row_dim),
nalgebra::Dynamic::new(#col_dim),
vec!#array_tokens))
};
proc_macro::TokenStream::from(output)
}
struct Vector {
elements: Vec<Expr>,
}
impl Vector {
fn to_array_tokens(&self) -> TokenStream2 {
let mut data = TokenStream2::new();
data.append_separated(&self.elements, Punct::new(',', Spacing::Alone));
TokenStream2::from(TokenTree::Group(Group::new(Delimiter::Bracket, data)))
}
fn len(&self) -> usize {
self.elements.len()
}
}
impl Parse for Vector {
fn parse(input: ParseStream) -> Result<Self> {
// The syntax of a vector is just the syntax of a single matrix row
if input.is_empty() {
Ok(Self {
elements: Vec::new(),
})
} else {
let elements = MatrixRowSyntax::parse_separated_nonempty(input)?
.into_iter()
.collect();
Ok(Self { elements })
}
}
}
/// Construct a fixed-size column vector directly from data.
///
/// **Note: Requires the `macro` feature to be enabled (enabled by default)**.
///
/// Similarly to [`matrix!`], this macro facilitates easy construction of fixed-size vectors.
/// However, whereas the [`matrix!`] macro expects each row to be separated by a semi-colon,
/// the syntax of this macro is instead similar to `vec!`, in that the elements of the vector
/// are simply listed consecutively.
///
/// `vector!` is intended to be the most readable and performant way of constructing small,
/// fixed-size vectors, and it is usable in `const fn` contexts.
///
/// ## Examples
///
/// ```
/// use nalgebra::vector;
///
/// // Produces a Vector3<_> == SVector<_, 3>
/// let v = vector![1, 2, 3];
/// ```
#[proc_macro]
pub fn vector(stream: TokenStream) -> TokenStream {
let vector = parse_macro_input!(stream as Vector);
let len = vector.len();
let array_tokens = vector.to_array_tokens();
let output = quote! {
nalgebra::SVector::<_, #len>
::from_array_storage(nalgebra::ArrayStorage([#array_tokens]))
};
proc_macro::TokenStream::from(output)
}
/// Construct a dynamic column vector directly from data.
///
/// **Note: Requires the `macro` feature to be enabled (enabled by default)**.
///
/// The syntax is exactly the same as for [`vector!`], but instead of producing instances of
/// `SVector`, it produces instances of `DVector`. At the moment it is not usable
/// in `const fn` contexts.
///
/// ```
/// use nalgebra::dvector;
///
/// // Produces a DVector<_>
/// let v = dvector![1, 2, 3];
/// ```
#[proc_macro]
pub fn dvector(stream: TokenStream) -> TokenStream {
let vector = parse_macro_input!(stream as Vector);
let len = vector.len();
let array_tokens = vector.to_array_tokens();
let output = quote! {
nalgebra::DVector::<_>
::from_vec_storage(nalgebra::VecStorage::new(
nalgebra::Dynamic::new(#len),
nalgebra::Const::<1>,
vec!#array_tokens))
};
proc_macro::TokenStream::from(output)
}

View File

@ -0,0 +1,258 @@
use nalgebra::{
DMatrix, DVector, Matrix1x2, Matrix1x3, Matrix1x4, Matrix2, Matrix2x1, Matrix2x3, Matrix2x4,
Matrix3, Matrix3x1, Matrix3x2, Matrix3x4, Matrix4, Matrix4x1, Matrix4x2, Matrix4x3, SMatrix,
SVector, Vector1, Vector2, Vector3, Vector4, Vector5, Vector6,
};
use nalgebra_macros::{dmatrix, dvector, matrix, vector};
fn check_statically_same_type<T>(_: &T, _: &T) {}
/// Wrapper for `assert_eq` that also asserts that the types are the same
macro_rules! assert_eq_and_type {
($left:expr, $right:expr $(,)?) => {
check_statically_same_type(&$left, &$right);
assert_eq!($left, $right);
};
}
// Skip rustfmt because it just makes the test bloated without making it more readable
#[rustfmt::skip]
#[test]
fn matrix_small_dims_exhaustive() {
// 0x0
assert_eq_and_type!(matrix![], SMatrix::<i32, 0, 0>::zeros());
// 1xN
assert_eq_and_type!(matrix![1], SMatrix::<i32, 1, 1>::new(1));
assert_eq_and_type!(matrix![1, 2], Matrix1x2::new(1, 2));
assert_eq_and_type!(matrix![1, 2, 3], Matrix1x3::new(1, 2, 3));
assert_eq_and_type!(matrix![1, 2, 3, 4], Matrix1x4::new(1, 2, 3, 4));
// 2xN
assert_eq_and_type!(matrix![1; 2], Matrix2x1::new(1, 2));
assert_eq_and_type!(matrix![1, 2; 3, 4], Matrix2::new(1, 2, 3, 4));
assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6], Matrix2x3::new(1, 2, 3, 4, 5, 6));
assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8], Matrix2x4::new(1, 2, 3, 4, 5, 6, 7, 8));
// 3xN
assert_eq_and_type!(matrix![1; 2; 3], Matrix3x1::new(1, 2, 3));
assert_eq_and_type!(matrix![1, 2; 3, 4; 5, 6], Matrix3x2::new(1, 2, 3, 4, 5, 6));
assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6; 7, 8, 9], Matrix3::new(1, 2, 3, 4, 5, 6, 7, 8, 9));
assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12],
Matrix3x4::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
// 4xN
assert_eq_and_type!(matrix![1; 2; 3; 4], Matrix4x1::new(1, 2, 3, 4));
assert_eq_and_type!(matrix![1, 2; 3, 4; 5, 6; 7, 8], Matrix4x2::new(1, 2, 3, 4, 5, 6, 7, 8));
assert_eq_and_type!(matrix![1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12],
Matrix4x3::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
assert_eq_and_type!(matrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12; 13, 14, 15, 16],
Matrix4::new(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16));
}
#[test]
fn matrix_const_fn() {
// Ensure that matrix! can be used in const contexts
const _: SMatrix<i32, 0, 0> = matrix![];
const _: SMatrix<i32, 1, 2> = matrix![1, 2];
const _: SMatrix<i32, 2, 3> = matrix![1, 2, 3; 4, 5, 6];
}
// Skip rustfmt because it just makes the test bloated without making it more readable
#[rustfmt::skip]
#[test]
fn dmatrix_small_dims_exhaustive() {
// 0x0
assert_eq_and_type!(dmatrix![], DMatrix::<i32>::zeros(0, 0));
// 1xN
assert_eq_and_type!(dmatrix![1], DMatrix::from_row_slice(1, 1, &[1]));
assert_eq_and_type!(dmatrix![1, 2], DMatrix::from_row_slice(1, 2, &[1, 2]));
assert_eq_and_type!(dmatrix![1, 2, 3], DMatrix::from_row_slice(1, 3, &[1, 2, 3]));
assert_eq_and_type!(dmatrix![1, 2, 3, 4], DMatrix::from_row_slice(1, 4, &[1, 2, 3, 4]));
// 2xN
assert_eq_and_type!(dmatrix![1; 2], DMatrix::from_row_slice(2, 1, &[1, 2]));
assert_eq_and_type!(dmatrix![1, 2; 3, 4], DMatrix::from_row_slice(2, 2, &[1, 2, 3, 4]));
assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6], DMatrix::from_row_slice(2, 3, &[1, 2, 3, 4, 5, 6]));
assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8], DMatrix::from_row_slice(2, 4, &[1, 2, 3, 4, 5, 6, 7, 8]));
// 3xN
assert_eq_and_type!(dmatrix![1; 2; 3], DMatrix::from_row_slice(3, 1, &[1, 2, 3]));
assert_eq_and_type!(dmatrix![1, 2; 3, 4; 5, 6], DMatrix::from_row_slice(3, 2, &[1, 2, 3, 4, 5, 6]));
assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6; 7, 8, 9], DMatrix::from_row_slice(3, 3, &[1, 2, 3, 4, 5, 6, 7, 8, 9]));
assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12],
DMatrix::from_row_slice(3, 4, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]));
// 4xN
assert_eq_and_type!(dmatrix![1; 2; 3; 4], DMatrix::from_row_slice(4, 1, &[1, 2, 3, 4]));
assert_eq_and_type!(dmatrix![1, 2; 3, 4; 5, 6; 7, 8], DMatrix::from_row_slice(4, 2, &[1, 2, 3, 4, 5, 6, 7, 8]));
assert_eq_and_type!(dmatrix![1, 2, 3; 4, 5, 6; 7, 8, 9; 10, 11, 12],
DMatrix::from_row_slice(4, 3, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]));
assert_eq_and_type!(dmatrix![1, 2, 3, 4; 5, 6, 7, 8; 9, 10, 11, 12; 13, 14, 15, 16],
DMatrix::from_row_slice(4, 4, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]));
}
// Skip rustfmt because it just makes the test bloated without making it more readable
#[rustfmt::skip]
#[test]
fn vector_small_dims_exhaustive() {
assert_eq_and_type!(vector![], SVector::<i32, 0>::zeros());
assert_eq_and_type!(vector![1], Vector1::<i32>::new(1));
assert_eq_and_type!(vector![1, 2], Vector2::new(1, 2));
assert_eq_and_type!(vector![1, 2, 3], Vector3::new(1, 2, 3));
assert_eq_and_type!(vector![1, 2, 3, 4], Vector4::new(1, 2, 3, 4));
assert_eq_and_type!(vector![1, 2, 3, 4, 5], Vector5::new(1, 2, 3, 4, 5));
assert_eq_and_type!(vector![1, 2, 3, 4, 5, 6], Vector6::new(1, 2, 3, 4, 5, 6));
}
#[test]
fn vector_const_fn() {
// Ensure that vector! can be used in const contexts
const _: SVector<i32, 0> = vector![];
const _: Vector1<i32> = vector![1];
const _: Vector2<i32> = vector![1, 2];
const _: Vector6<i32> = vector![1, 2, 3, 4, 5, 6];
}
// Skip rustfmt because it just makes the test bloated without making it more readable
#[rustfmt::skip]
#[test]
fn dvector_small_dims_exhaustive() {
assert_eq_and_type!(dvector![], DVector::<i32>::zeros(0));
assert_eq_and_type!(dvector![1], DVector::from_column_slice(&[1]));
assert_eq_and_type!(dvector![1, 2], DVector::from_column_slice(&[1, 2]));
assert_eq_and_type!(dvector![1, 2, 3], DVector::from_column_slice(&[1, 2, 3]));
assert_eq_and_type!(dvector![1, 2, 3, 4], DVector::from_column_slice(&[1, 2, 3, 4]));
assert_eq_and_type!(dvector![1, 2, 3, 4, 5], DVector::from_column_slice(&[1, 2, 3, 4, 5]));
assert_eq_and_type!(dvector![1, 2, 3, 4, 5, 6], DVector::from_column_slice(&[1, 2, 3, 4, 5, 6]));
}
#[test]
fn matrix_trybuild_tests() {
let t = trybuild::TestCases::new();
// Verify error message when we give a matrix with mismatched dimensions
t.compile_fail("tests/trybuild/matrix_mismatched_dimensions.rs");
}
#[test]
fn dmatrix_trybuild_tests() {
let t = trybuild::TestCases::new();
// Verify error message when we give a matrix with mismatched dimensions
t.compile_fail("tests/trybuild/dmatrix_mismatched_dimensions.rs");
}
#[test]
fn matrix_builtin_types() {
// Check that matrix! compiles for all built-in types
const _: SMatrix<i8, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<i16, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<i32, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<i64, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<isize, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<u8, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<u16, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<u32, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<u64, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<usize, 2, 2> = matrix![0, 1; 2, 3];
const _: SMatrix<f32, 2, 2> = matrix![0.0, 1.0; 2.0, 3.0];
const _: SMatrix<f64, 2, 2> = matrix![0.0, 1.0; 2.0, 3.0];
}
#[test]
fn vector_builtin_types() {
// Check that vector! compiles for all built-in types
const _: SVector<i8, 4> = vector![0, 1, 2, 3];
const _: SVector<i16, 4> = vector![0, 1, 2, 3];
const _: SVector<i32, 4> = vector![0, 1, 2, 3];
const _: SVector<i64, 4> = vector![0, 1, 2, 3];
const _: SVector<isize, 4> = vector![0, 1, 2, 3];
const _: SVector<u8, 4> = vector![0, 1, 2, 3];
const _: SVector<u16, 4> = vector![0, 1, 2, 3];
const _: SVector<u32, 4> = vector![0, 1, 2, 3];
const _: SVector<u64, 4> = vector![0, 1, 2, 3];
const _: SVector<usize, 4> = vector![0, 1, 2, 3];
const _: SVector<f32, 4> = vector![0.0, 1.0, 2.0, 3.0];
const _: SVector<f64, 4> = vector![0.0, 1.0, 2.0, 3.0];
}
#[test]
fn dmatrix_builtin_types() {
// Check that dmatrix! compiles for all built-in types
let _: DMatrix<i8> = dmatrix![0, 1; 2, 3];
let _: DMatrix<i16> = dmatrix![0, 1; 2, 3];
let _: DMatrix<i32> = dmatrix![0, 1; 2, 3];
let _: DMatrix<i64> = dmatrix![0, 1; 2, 3];
let _: DMatrix<isize> = dmatrix![0, 1; 2, 3];
let _: DMatrix<u8> = dmatrix![0, 1; 2, 3];
let _: DMatrix<u16> = dmatrix![0, 1; 2, 3];
let _: DMatrix<u32> = dmatrix![0, 1; 2, 3];
let _: DMatrix<u64> = dmatrix![0, 1; 2, 3];
let _: DMatrix<usize> = dmatrix![0, 1; 2, 3];
let _: DMatrix<f32> = dmatrix![0.0, 1.0; 2.0, 3.0];
let _: DMatrix<f64> = dmatrix![0.0, 1.0; 2.0, 3.0];
}
#[test]
fn dvector_builtin_types() {
// Check that dvector! compiles for all built-in types
let _: DVector<i8> = dvector![0, 1, 2, 3];
let _: DVector<i16> = dvector![0, 1, 2, 3];
let _: DVector<i32> = dvector![0, 1, 2, 3];
let _: DVector<i64> = dvector![0, 1, 2, 3];
let _: DVector<isize> = dvector![0, 1, 2, 3];
let _: DVector<u8> = dvector![0, 1, 2, 3];
let _: DVector<u16> = dvector![0, 1, 2, 3];
let _: DVector<u32> = dvector![0, 1, 2, 3];
let _: DVector<u64> = dvector![0, 1, 2, 3];
let _: DVector<usize> = dvector![0, 1, 2, 3];
let _: DVector<f32> = dvector![0.0, 1.0, 2.0, 3.0];
let _: DVector<f64> = dvector![0.0, 1.0, 2.0, 3.0];
}
/// Black box function that's just used for testing macros with function call expressions.
fn f<T>(x: T) -> T {
x
}
#[rustfmt::skip]
#[test]
fn matrix_arbitrary_expressions() {
// Test that matrix! supports arbitrary expressions for its elements
let a = matrix![1 + 2 , 2 * 3;
4 * f(5 + 6), 7 - 8 * 9];
let a_expected = Matrix2::new(1 + 2 , 2 * 3,
4 * f(5 + 6), 7 - 8 * 9);
assert_eq_and_type!(a, a_expected);
}
#[rustfmt::skip]
#[test]
fn dmatrix_arbitrary_expressions() {
// Test that dmatrix! supports arbitrary expressions for its elements
let a = dmatrix![1 + 2 , 2 * 3;
4 * f(5 + 6), 7 - 8 * 9];
let a_expected = DMatrix::from_row_slice(2, 2, &[1 + 2 , 2 * 3,
4 * f(5 + 6), 7 - 8 * 9]);
assert_eq_and_type!(a, a_expected);
}
#[rustfmt::skip]
#[test]
fn vector_arbitrary_expressions() {
// Test that vector! supports arbitrary expressions for its elements
let a = vector![1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9];
let a_expected = Vector4::new(1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9);
assert_eq_and_type!(a, a_expected);
}
#[rustfmt::skip]
#[test]
fn dvector_arbitrary_expressions() {
// Test that dvector! supports arbitrary expressions for its elements
let a = dvector![1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9];
let a_expected = DVector::from_column_slice(&[1 + 2, 2 * 3, 4 * f(5 + 6), 7 - 8 * 9]);
assert_eq_and_type!(a, a_expected);
}

View File

@ -0,0 +1,6 @@
use nalgebra_macros::dmatrix;
fn main() {
dmatrix![1, 2, 3;
4, 5];
}

View File

@ -0,0 +1,5 @@
error: Unexpected number of entries in row 1. Expected 3, found 2 entries.
--> $DIR/dmatrix_mismatched_dimensions.rs:5:13
|
5 | 4, 5];
| ^

View File

@ -0,0 +1,6 @@
use nalgebra_macros::matrix;
fn main() {
matrix![1, 2, 3;
4, 5];
}

View File

@ -0,0 +1,5 @@
error: Unexpected number of entries in row 1. Expected 3, found 2 entries.
--> $DIR/matrix_mismatched_dimensions.rs:5:13
|
5 | 4, 5];
| ^

View File

@ -29,7 +29,10 @@ use crate::base::storage::{
ContiguousStorage, ContiguousStorageMut, Owned, SameShapeStorage, Storage, StorageMut,
};
use crate::base::{Const, DefaultAllocator, OMatrix, OVector, Scalar, Unit};
use crate::SimdComplexField;
use crate::{ArrayStorage, SMatrix, SimdComplexField};
#[cfg(any(feature = "std", feature = "alloc"))]
use crate::{DMatrix, DVector, Dynamic, VecStorage};
/// A square matrix.
pub type SquareMatrix<T, D, S> = Matrix<T, D, D, S>;
@ -317,6 +320,49 @@ impl<T, R, C, S> Matrix<T, R, C, S> {
}
}
impl<T, const R: usize, const C: usize> SMatrix<T, R, C> {
/// Creates a new statically-allocated matrix from the given [ArrayStorage].
///
/// This method exists primarily as a workaround for the fact that `from_data` can not
/// work in `const fn` contexts.
#[inline(always)]
pub const fn from_array_storage(storage: ArrayStorage<T, R, C>) -> Self {
// This is sound because the row and column types are exactly the same as that of the
// storage, so there can be no mismatch
unsafe { Self::from_data_statically_unchecked(storage) }
}
}
// TODO: Consider removing/deprecating `from_vec_storage` once we are able to make
// `from_data` const fn compatible
#[cfg(any(feature = "std", feature = "alloc"))]
impl<T> DMatrix<T> {
/// Creates a new heap-allocated matrix from the given [VecStorage].
///
/// This method exists primarily as a workaround for the fact that `from_data` can not
/// work in `const fn` contexts.
pub const fn from_vec_storage(storage: VecStorage<T, Dynamic, Dynamic>) -> Self {
// This is sound because the dimensions of the matrix and the storage are guaranteed
// to be the same
unsafe { Self::from_data_statically_unchecked(storage) }
}
}
// TODO: Consider removing/deprecating `from_vec_storage` once we are able to make
// `from_data` const fn compatible
#[cfg(any(feature = "std", feature = "alloc"))]
impl<T> DVector<T> {
/// Creates a new heap-allocated matrix from the given [VecStorage].
///
/// This method exists primarily as a workaround for the fact that `from_data` can not
/// work in `const fn` contexts.
pub const fn from_vec_storage(storage: VecStorage<T, Dynamic, U1>) -> Self {
// This is sound because the dimensions of the matrix and the storage are guaranteed
// to be the same
unsafe { Self::from_data_statically_unchecked(storage) }
}
}
impl<T: Scalar, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
/// Creates a new matrix with the given data.
#[inline(always)]

View File

@ -136,6 +136,9 @@ pub use crate::sparse::*;
)]
pub use base as core;
#[cfg(feature = "macros")]
pub use nalgebra_macros::{dmatrix, dvector, matrix, vector};
use simba::scalar::SupersetOf;
use std::cmp::{self, Ordering, PartialOrd};

11
tests/core/macros.rs Normal file
View File

@ -0,0 +1,11 @@
use nalgebra::{dmatrix, dvector, matrix, vector};
#[test]
fn sanity_test() {
// The macros are already tested in `nalgebra-macros`. Here we just test that they compile fine.
let _ = matrix![1, 2, 3; 4, 5, 6];
let _ = dmatrix![1, 2, 3; 4, 5, 6];
let _ = vector![1, 2, 3, 4, 5, 6];
let _ = dvector![1, 2, 3, 4, 5, 6];
}

View File

@ -16,3 +16,6 @@ mod matrixcompare;
#[cfg(feature = "arbitrary")]
pub mod helper;
#[cfg(feature = "macros")]
mod macros;

View File

@ -1,7 +1,12 @@
#[cfg(not(all(feature = "debug", feature = "compare", feature = "rand")))]
#[cfg(not(all(
feature = "debug",
feature = "compare",
feature = "rand",
feature = "macros"
)))]
compile_error!(
"Please enable the `debug`, `compare`, and `rand` features in order to compile and run the tests.
Example: `cargo test --features debug,compare,rand`"
"Please enable the `debug`, `compare`, `rand` and `macros` features in order to compile and run the tests.
Example: `cargo test --features debug,compare,rand,macros`"
);
#[cfg(feature = "abomonation-serialize")]