diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 000000000..d4ca0f39b --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,36 @@ +name: github pages + +on: + push: + branches: + - main + release: + types: [created] + pull_request: + +jobs: + deploy: + runs-on: ubuntu-20.04 + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + steps: + - uses: actions/checkout@v3 + + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup mdBook + uses: peaceiris/actions-mdbook@v1 + with: + mdbook-version: 'latest' + + - run: mdbook build book + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + if: github.event_name == 'release' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./book/book diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 000000000..cc8de95c6 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,9 @@ +[book] +authors = ["Kevin R. Thornton"] +language = "en" +multilingual = false +src = "src" +title = "The tskit (rust) book" + +[output.html] +preferred-dark-theme = "light" diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 000000000..df6c522e1 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,7 @@ +# Summary + +- [Introduction](./introduction.md) +- [Working with table collections](./working_with_table_collections.md) + - [Creation](./table_collection_creation.md) + - [Adding rows](./table_collection_adding_rows.md) + - [Validating table collection contents](./table_collection_validation.md) diff --git a/book/src/introduction.md b/book/src/introduction.md new file mode 100644 index 000000000..dc6325e35 --- /dev/null +++ b/book/src/introduction.md @@ -0,0 +1,42 @@ +# Introduction + +## Do you need `tskit-rust`? + +The use-cases for `tskit-rust` are the same as for `tskit-c`: + +1. Developing a new performance-oriented application. +2. The input/output of this application will be a `.trees` file. + +## What does `tskit-rust` add? + +Briefly, you get the performance of `C` and the strong safety guarantees of `rust`. + +## What is `tskit-rust` missing? + +The crate does not cover the entire `C` API. +However, client code can make direct calls to that API via the module `tskit::bindings`. + +## Adding `tskit` as a dependency to a rust project + +In your `Cargo.toml`: + +```{toml} +[dependencies] +tskit = "~X.Y.Z" +``` + +The latest version to fill in `X.Y.Z` can be found [here](https://crates.io/crates/tskit). +See [here](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html) for how to specify version numbers. + +### Feature flags. + +`tskit` defines several [cargo features](https://doc.rust-lang.org/cargo/reference/features.html). +These are defined in the [API docs](https://docs.rs/tskit/latest/tskit/#optional-features). + +## Conventions used in this document + +We assume a working knowledge of rust. +Thus, we skip over the details of things like matching idioms, etc., +and just `.unwrap()`. + +We also assume familiarity with the `tskit` [data model](https://tskit.dev/tskit/docs/stable/data-model.html). diff --git a/book/src/table_collection_adding_rows.md b/book/src/table_collection_adding_rows.md new file mode 100644 index 000000000..6db751228 --- /dev/null +++ b/book/src/table_collection_adding_rows.md @@ -0,0 +1,26 @@ +## Adding rows to tables + +* For each table type (node, edge., etc.), we have a function to add a row. +* We can only add rows to tables in mutable `TableCollection` instances. + +For example, to add a node: + + +```rust, noplaygound, ignore +{{#include ../../tests/book_table_collection.rs:add_node_without_metadata}} +``` + +We see from the `if let` pattern that functions adding rows return +[`Result`](https://doc.rust-lang.org/std/result/enum.Result.html). +In general, errors only occur when the C back-end fails to allocate memory +to expand the table columns. +If we add a row with invalid data, no error is returned! +To catch such errors, we must explicitly check table integrity (see [below](table_collection_validation.md#checking-table-integrity)). + +Again, we can take advantage of being able to pass in any type that is `Into<_>` the required newtype: + +```rust, noplaygound, ignore +{{#include ../../tests/book_table_collection.rs:add_node_without_metadata_using_into}} +``` + +See the [API docs](https://docs.rs/tskit) for more details and examples. diff --git a/book/src/table_collection_creation.md b/book/src/table_collection_creation.md new file mode 100644 index 000000000..7246ca285 --- /dev/null +++ b/book/src/table_collection_creation.md @@ -0,0 +1,18 @@ +## Creation + +We initialize a `TableCollection` with a sequence length. +In `tskit-c`, the genome length is a C `double`. +Here it is a [newtype](https://doc.rust-lang.org/rust-by-example/generics/new_types.html) called `tskit::Position`: + +```rust, noplaygound, ignore +{{#include ../../tests/book_table_collection.rs:create_table_collection_with_newtype}} +``` + +The newtype pattern gives type safety by disallowing you to send a position to a function where a time is required, etc.. +However, it can be inconvenient to type out the full type names every time. +Thus, the API defines most functions taking arguments `Into` where `T` is one of our newtypes. +This design means that the following is equivalent to what we wrote above: + +```rust, noplaygound, ignore +{{#include ../../tests/book_table_collection.rs:create_table_collection}} +``` diff --git a/book/src/table_collection_validation.md b/book/src/table_collection_validation.md new file mode 100644 index 000000000..69d186207 --- /dev/null +++ b/book/src/table_collection_validation.md @@ -0,0 +1,11 @@ +## Checking table integrity + +The data model involves lazy checking of inputs. +In other words, we can add invalid row data that is not caught by the "add row" functions. +We inherit this behavior from the C API. + +We can check that the tables contain valid data by: + +```rust, noplaygound, ignore +{{#include ../../tests/book_table_collection.rs:integrity_check}} +``` diff --git a/book/src/working_with_table_collections.md b/book/src/working_with_table_collections.md new file mode 100644 index 000000000..7dcbb8ff3 --- /dev/null +++ b/book/src/working_with_table_collections.md @@ -0,0 +1,3 @@ +# Working with table collections + +The next sections cover how to work with the `TableCollection`, which is one of `tskit`'s fundamental types. diff --git a/tests/book_table_collection.rs b/tests/book_table_collection.rs new file mode 100644 index 000000000..b53988ecb --- /dev/null +++ b/tests/book_table_collection.rs @@ -0,0 +1,68 @@ +#[test] +fn simple_table_collection_creation_with_newtype() { + // ANCHOR: create_table_collection_with_newtype + let sequence_length = tskit::Position::from(100.0); + if let Ok(tables) = tskit::TableCollection::new(sequence_length) { + assert_eq!(tables.sequence_length(), sequence_length); + // In tskit, the various newtypes can be compared to + // the low-level types they wrap. + assert_eq!(tables.sequence_length(), 100.0); + } else { + panic!( + "TableCollection creation sequence length = {} failed", + sequence_length + ); + } + // ANCHOR_END: create_table_collection_with_newtype +} + +#[test] +fn simple_table_collection_creation() { + // ANCHOR: create_table_collection + let tables = tskit::TableCollection::new(100.0).unwrap(); + // ANCHOR_END: create_table_collection + assert_eq!(tables.sequence_length(), 100.0); +} + +#[test] +fn add_node_without_metadata() { + { + // ANCHOR: add_node_without_metadata + let mut tables = tskit::TableCollection::new(100.0).unwrap(); + if let Ok(node_id) = tables.add_node( + 0, // Node flags + tskit::Time::from(0.0), // Birth time + tskit::PopulationId::NULL, // Population id + tskit::IndividualId::NULL, // Individual id + ) { + assert_eq!(node_id, 0); + } + // ANCHOR_END: add_node_without_metadata + } + { + let mut tables = tskit::TableCollection::new(100.0).unwrap(); + // ANCHOR: add_node_without_metadata_using_into + let node_id = tables.add_node(0, 0.0, -1, -1).unwrap(); + // ANCHOR_END: add_node_without_metadata_using_into + assert_eq!(node_id, 0); + } +} + +#[test] +fn add_node_handle_error() { + // ANCHOR: integrity_check + let mut tables = tskit::TableCollection::new(100.0).unwrap(); + // Everything about this edge is wrong... + tables.add_edge(-1.0, 110.0, 0, 1).unwrap(); + // ...and we can catch that here + match tables.check_integrity(tskit::TableIntegrityCheckFlags::default()) { + Ok(code) => panic!("expected Err(e) but got code: {}", code), + // tskit::TskitError can be formatted into the same + // error messages that tskit-c/tskit-python give. + Err(e) => println!("{}", e), + } + // ANCHOR_END: integrity_check + assert!(tables + .check_integrity(tskit::TableIntegrityCheckFlags::default()) + .is_err()); +}