**Goal:** Build a two-state Markov chain in Rust that models daily weather as either `Sunny` or `Rainy`, driven by a transition matrix, and simulate 30 days of weather.
**Goal:** Build a two-state Markov chain in Rust that models daily weather as either `Sunny` or `Rainy`, driven by a transition matrix, and simulate 30 days of weather.
#### Setup
Create a new Cargo binary project and add the `rand` crate:
```sh
cargo new weather-chain
cd weather-chain
cargo add rand
```
#### Starter Code
Replace the contents of `src/main.rs` with the following skeleton. Do not change the struct layout or function signatures — your task is to fill in the `todo!()` bodies and write `main`.
`step` needs to index into `self.transition` using the current state, and later convert an index back to a `Weather` value. Add two helper methods to the `Weather` enum:
- `fn index(self) -> usize` — returns `0` for `Sunny`, `1` for `Rainy`
- `fn from_index(i: usize) -> Self` — returns `Sunny` for `0`, `Rainy` for anything else
A simple `match` expression handles both. These are the only tools you need to bridge the enum and the matrix.
#### Step 2 — Implement `WeatherChain::step`
`step` must sample the next state from the probability row for the current state. The technique is a **cumulative-probability walk**:
1. Retrieve the transition row: `let row = self.transition[current.index()];`
2. Draw a uniform float in [0, 1): `let r: f64 = rng.gen();`
3. Walk the row, accumulating probability. When the running total first exceeds `r`, return that state.
For the two-state case this reduces to a single comparison — if `r < row[0]` return `Sunny`, otherwise return `Rainy` — but implementing the loop works for any number of states and is worth practising.
Add a fallback `Weather::from_index(row.len() - 1)` after the loop to satisfy the compiler; floating-point rounding can in rare cases leave the loop without returning.
#### Step 3 — Implement `WeatherChain::simulate`
`simulate` runs the chain for `steps` transitions, collecting every state visited including the start:
The returned slice will have length `steps + 1` (the initial state plus one state per step).
#### Step 4 — Run the simulation
In `main`, create a `WeatherChain` with the two-state matrix from Section 3:
```
Sunny [0.8, 0.2]
Rainy [0.4, 0.6]
```
Seed a repeatable RNG with `rand::rngs::SmallRng::seed_from_u64(42)` (add `use rand::SeedableRng;`). Simulate 30 steps starting from `Sunny`, print the resulting sequence, and count how many days were sunny vs rainy.
Expected output structure (exact numbers vary by seed):
```
[Sunny, Sunny, Rainy, Sunny, ...]
Sunny days: 21 (67.7%)
Rainy days: 10 (32.3%)
```
#### Step 5 — Compare to the stationary distribution
The **stationary distribution** π satisfies π = πP, meaning once the chain reaches it, the distribution no longer changes. For this matrix, solve the two equations:
```
π₀ = 0.8·π₀ + 0.4·π₁
π₁ = 0.2·π₀ + 0.6·π₁
π₀ + π₁ = 1
```
The first equation simplifies to `0.2·π₀ = 0.4·π₁`, giving `π₀ = 2·π₁`. Substituting into the normalisation constraint: `π₀ = 2/3 ≈ 66.7%`, `π₁ = 1/3 ≈ 33.3%`.
Print the stationary percentages alongside your simulated counts. With only 31 data points the match will be rough; re-run with 1 000 or 10 000 steps to see the empirical frequencies converge.