Skip to main content

bimm_contracts/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3#![deny(unused_must_use)]
4#![warn(missing_docs)]
5//! # bimm-contracts
6//!
7//! This is a ``no_std`` inline contract programming library for tensor geometry
8//! for the [burn](https://burn.dev) tensor framework.
9//!
10//! Contract programming, or [Design by Contract](https://en.wikipedia.org/wiki/Design_by_contract),
11//! is a programming paradigm that specifies the rights and obligations of software components.
12//!
13//! The goal of this library is to make in-line geometry contracts:
14//! * Easy to Read, Write, and Use,
15//! * Performant at Runtime (so they can always be enabled),
16//! * Verbose and Helpful in their error messages.
17//!
18//! ## Features
19//!
20//! - ``burn``: Shape support for [burn](https://burn.dev) types:
21//!    - `&Tensor`, `&Shape`, `Shape`.
22//!
23//! ## API
24//!
25//! Users will primarily use the macros:
26//! - [`unpack_shape_contract`],
27//! - [`assert_shape_contract`], and
28//! - [`assert_shape_contract_periodically`].
29//!
30//! For example:
31//! ```rust,no_run
32//! use bimm_contracts::unpack_shape_contract;
33//!
34//! let shape = [12, 3 * 4, 5 * 4, 3];
35//!
36//! // In release builds, this has a benchmark of ~160ns:
37//! let [b, h_wins, w_wins, c] = unpack_shape_contract!(
38//!     [
39//!         "batch",
40//!         "height" = "h_wins" * "window_size",
41//!         "width" = "w_wins" * "window_size",
42//!         "channels"
43//!     ],
44//!     &shape,
45//!     &["batch", "h_wins", "w_wins", "channels"],
46//!     &[("window_size", 4)],
47//! );
48//!
49//! assert_eq!(b, 12);
50//! assert_eq!(h_wins, 3);
51//! assert_eq!(w_wins, 4);
52//! assert_eq!(c, 3);
53//! ```
54//!
55//! In turn, these macros wrap the layer 2 api:
56//! * [`shape_contract`] - a macro for defining shape contracts from expressions.
57//! * [`define_shape_contract`] - a macro for defining a static contract.
58//! * [`ShapeContract`] - the constructed contract type.
59//!   * [`ShapeContract::assert_shape`] - assert a contract.
60//!   * [`ShapeContract::unpack_shape`] - assert a contract, and unpack geometry components.
61//! * [`run_periodically`] - a macro for running code on an incrementally lengthening schedule.
62//!
63//! ### `ShapeArgument` Support
64//!
65//! The shape methods take a [`ShapeArgument`] parameter; with implementations for:
66//! * ``&[usize]``, ``&[usize; D]``,
67//! * ``&[u32]``, ``&[u32; D]``,
68//! * ``&[i32]``, ``&[i32; D]``,
69//! * ``&Vec<usize>``,
70//! * ``&Vec<u32>``,
71//! * ``&Vec<i32>``
72//!
73//! With ``features = ["burn"]``:
74//! * ``burn::prelude::Shape``,
75//! * ``&burn::prelude::Shape``,
76//! * ``&burn::prelude::Tensor``
77//!
78//! ## Speed and Stack Design
79//!
80//! Contracts are only useful when they are fast enough to be always enabled.
81//!
82//! As a result, this library is designed to be fast at runtime,
83//! focusing on `static` contracts and using stack over heap wherever possible.
84//!
85//! Benchmarks on release builds are available under ``cargo bench -p bimm-contracts``:
86//!
87//! ```terminaloutput
88//! Running benches/contracts.rs (target/release/deps/contracts-86950340ff3748c1)
89//! unpack_shape            time:   [176.03 ns 177.39 ns 178.81 ns]
90//! Found 2 outliers among 100 measurements (2.00%)
91//! 1 (1.00%) high mild
92//! 1 (1.00%) high severe
93//!
94//! assert_shape            time:   [166.57 ns 168.00 ns 169.60 ns]
95//! Found 2 outliers among 100 measurements (2.00%)
96//! 1 (1.00%) high mild
97//! 1 (1.00%) high severe
98//!
99//! assert_shape_every_nth/assert_shape_every_nth
100//! time:   [4.4057 ns 4.4769 ns 4.5726 ns]
101//! Found 14 outliers among 100 measurements (14.00%)
102//! 6 (6.00%) high mild
103//! 8 (8.00%) high severe
104//! ```
105//!
106//! ## `shape_contract`! macro
107//!
108//! The `shape_contract!` macro is a compile-time macro that parses a shape contract
109//! from a shape contract pattern:
110//!
111//! ```rust
112//! use bimm_contracts::{ShapeContract, shape_contract};
113//! static CONTRACT: ShapeContract = shape_contract![_, "w" = "x" + "y", ..., "z" ^ 2];
114//! ```
115//!
116//! A shape pattern is made of one or more dimension matcher terms:
117//! - `_`: for any shape; ignores the size, but requires the dimension to exist.,
118//! - `...`: for ellipsis; matches any number of dimensions, only one ellipsis is allowed,
119//! - a dim expression.
120//!
121//! ```bnf
122//! ShapeContract => <LabeledExpr> { ',' <LabeledExpr> }* ','?
123//! LabeledExpr => {Param "="}? <Expr>
124//! Expr => <Term> { <AddOp> <Term> }
125//! Term => <Power> { <MulOp> <Power> }
126//! Power => <Factor> [ ^ <usize> ]
127//! Factor => <Param> | ( '(' <Expression> ')' ) | NegOp <Factor>
128//! Param => '"' <identifier> '"'
129//! identifier => { <alpha> | "_" } { <alphanumeric> | "_" }*
130//! NegOp =>      '+' | '-'
131//! AddOp =>      '+' | '-'
132//! MulOp =>      '*'
133//! ```
134//!
135//! ## Usage Example
136//!
137//! ```rust,ignore
138//! use burn::prelude::{Tensor, Backend};
139//! use burn::tensor::BasicOps;
140//! use bimm_contracts::{unpack_shape_contract, assert_shape_contract_periodically};
141//!
142//! /// Window Partition
143//! ///
144//! /// ## Parameters
145//! ///
146//! /// - `tensor`: Input tensor of shape (B, h_wins * window_size, w_wins * window_size, C).
147//! /// - `window_size`: Window size.
148//! ///
149//! /// ## Returns
150//! ///
151//! /// Output tensor of shape (B * h_windows * w_windows, window_size, window_size, C).
152//! ///
153//! /// ## Panics
154//! ///
155//! /// Panics if the input tensor does not have 4 dimensions.
156//! pub fn window_partition<B: Backend, K>(
157//!     tensor: Tensor<B, 4, K>,
158//!     window_size: usize,
159//! ) -> Tensor<B, 4, K>
160//! where
161//!     K: BasicOps<B>,
162//! {
163//!     // In release builds, this has a benchmark of ~160ns:
164//!     let [b, h_wins, w_wins, c] = unpack_shape_contract!(
165//!         [
166//!             "batch",
167//!             "height" = "h_wins" * "window_size",
168//!             "width" = "w_wins" * "window_size",
169//!             "channels"
170//!         ],
171//!         &tensor,
172//!         &["batch", "h_wins", "w_wins", "channels"],
173//!         &[("window_size", window_size)],
174//!     );
175//!
176//!     let tensor = tensor
177//!         .reshape([b, h_wins, window_size, w_wins, window_size, c])
178//!         .swap_dims(2, 3)
179//!         .reshape([b * h_wins * w_wins, window_size, window_size, c]);
180//!
181//!     // Run an amortized check on the output shape.
182//!     //
183//!     // `run_periodically!{}` runs the first 10 times,
184//!     // then on an incrementally lengthening schedule,
185//!     // until it reaches its default period of 1000.
186//!     //
187//!     // Due to amortization, in release builds, this averages ~4ns:
188//!     assert_shape_contract_periodically!(
189//!         [
190//!             "batch" * "h_wins" * "w_wins",
191//!             "window_size",
192//!             "window_size",
193//!             "channels"
194//!         ],
195//!         &tensor,
196//!         &[
197//!             ("batch", b),
198//!             ("h_wins", h_wins),
199//!             ("w_wins", w_wins),
200//!             ("window_size", window_size),
201//!             ("channels", c),
202//!         ]
203//!     );
204//!
205//!     tensor
206//! }
207//! ```
208//! ## Error Messages
209//!
210//! Error messages are verbose and helpful.
211//!
212//! ```rust
213//! use bimm_contracts::{ShapeContract, shape_contract};
214//! use indoc::indoc;
215//!
216//! fn example() {
217//!     static CONTRACT: ShapeContract = shape_contract![
218//!             ...,
219//!             "height" = "h_wins" * "window",
220//!             "width" = "w_wins" * "window",
221//!             "color",
222//!         ];
223//!
224//!     let h_wins = 2;
225//!     let w_wins = 3;
226//!     let window = 4;
227//!     let color = 3;
228//!
229//!     let shape = [1, 2, 3, h_wins * window, w_wins * window, color];
230//!
231//!     let [h, w] = CONTRACT.unpack_shape(&shape, &["h_wins", "w_wins"], &[
232//!         ("window", window),
233//!         ("color", color),
234//!     ]);
235//!     assert_eq!(h, h_wins);
236//!     assert_eq!(w, w_wins);
237//!
238//!     assert_eq!(
239//!         CONTRACT.try_unpack_shape(&shape, &["h_wins", "w_wins"], &[
240//!             ("window", window + 1),
241//!             ("color", color),
242//!         ]).unwrap_err(),
243//!         indoc! {r#"
244//!             Shape Error:: 8 !~ height=(h_wins*window) :: No integer solution.
245//!              shape:
246//!               [1, 2, 3, 8, 12, 3]
247//!              expected:
248//!               [..., height=(h_wins*window), width=(w_wins*window), color]
249//!               {"window": 5, "color": 3}"#
250//!         },
251//!     );
252//! }
253//! ```
254
255extern crate alloc;
256
257pub use bimm_contracts_macros::shape_contract;
258
259pub mod bindings;
260pub mod contracts;
261pub mod expressions;
262pub mod macros;
263pub mod math;
264pub mod shape_argument;
265pub mod support;
266
267pub use bindings::StackEnvironment;
268pub use contracts::{DimMatcher, ShapeContract};
269pub use expressions::DimExpr;
270pub use shape_argument::ShapeArgument;