Skip to main content

solve_batch

Function solve_batch 

Source
pub fn solve_batch(
    accounts: &[BatchAccount],
    settings: Option<SolverSettings>,
) -> Vec<Result<Vec<Solution>, PortfolioError>>
Expand description

Solves every account’s rolling sequence, in parallel over the account axis when the rayon cargo feature is enabled (roadmap 3.2).

Each account gets its own workspace (equilibration and factorizations built once per account, warm starts chained across its dates), so the batch does the same numerical work as calling solve_sequence per account — results are bit-identical to that loop regardless of the feature or thread count. One entry is returned per account, in input order; a failed account never affects the others.

Examples found in repository?
examples/batch.rs (line 144)
119fn main() -> Result<(), Box<dyn Error>> {
120    let mut config = Config::default();
121    let arguments: Vec<String> = env::args().skip(1).collect();
122    let mut index = 0;
123    while index < arguments.len() {
124        let flag = &arguments[index];
125        let value = arguments
126            .get(index + 1)
127            .ok_or_else(|| format!("missing value after {flag}"))?;
128        match flag.as_str() {
129            "--accounts" => config.accounts = value.parse()?,
130            "--dates" => config.dates = value.parse()?,
131            "--n" => config.assets = value.parse()?,
132            "--k" => config.factors = value.parse()?,
133            "--out" => config.out = Some(value.clone()),
134            _ => return Err(format!("unknown argument: {flag}").into()),
135        }
136        index += 2;
137    }
138
139    let build_started = Instant::now();
140    let accounts = build_accounts(&config)?;
141    let build_seconds = build_started.elapsed().as_secs_f64();
142
143    let solve_started = Instant::now();
144    let results = solve_batch(&accounts, None);
145    let wall_seconds = solve_started.elapsed().as_secs_f64();
146
147    let mut solved = 0_usize;
148    let mut unconverged = 0_usize;
149    let mut total_iterations = 0_usize;
150    let mut solver_seconds = 0.0_f64;
151    let mut rows = Vec::with_capacity(config.accounts);
152    for (account, result) in results.iter().enumerate() {
153        let solutions = result
154            .as_ref()
155            .map_err(|error| format!("account {account}: {error}"))?;
156        let mut account_iterations = 0_usize;
157        let mut account_solved = 0_usize;
158        let mut account_seconds = 0.0_f64;
159        for solution in solutions {
160            match solution.status {
161                SolveStatus::Solved => account_solved += 1,
162                _ => unconverged += 1,
163            }
164            account_iterations += solution.iterations;
165            account_seconds += solution.solve_time.as_secs_f64();
166        }
167        solved += account_solved;
168        total_iterations += account_iterations;
169        solver_seconds += account_seconds;
170        rows.push(format!(
171            "{account},{},{account_solved},{account_iterations},{:.3}",
172            solutions.len(),
173            1.0e3 * account_seconds
174        ));
175    }
176
177    let total_solves = config.accounts * config.dates;
178    println!(
179        "batch: {} accounts x {} dates, n={} assets, k={} factors, threads={} ({})",
180        config.accounts,
181        config.dates,
182        config.assets,
183        config.factors,
184        thread_count(),
185        if cfg!(feature = "rayon") {
186            "rayon"
187        } else {
188            "serial"
189        },
190    );
191    println!("account setup: {build_seconds:.2} s (problems + steps, single-threaded)");
192    println!(
193        "solve wall time: {wall_seconds:.2} s for {total_solves} account-dates \
194         => {:.0} solves/s",
195        total_solves as f64 / wall_seconds
196    );
197    println!(
198        "statuses: {solved} solved, {unconverged} other; iterations: {total_iterations} total, \
199         {:.1} mean/solve",
200        total_iterations as f64 / total_solves as f64
201    );
202    println!(
203        "solver time (iteration-only, summed across threads): {solver_seconds:.2} s, \
204         {:.3} ms mean/solve",
205        1.0e3 * solver_seconds / total_solves as f64
206    );
207
208    if let Some(path) = &config.out {
209        let mut csv = String::from("account,dates,solved,iterations,solve_time_ms\n");
210        for row in &rows {
211            csv.push_str(row);
212            csv.push('\n');
213        }
214        fs::write(path, csv)?;
215        println!("per-account samples written to {path}");
216    }
217
218    if unconverged > 0 {
219        return Err(format!("{unconverged} account-dates did not reach Solved").into());
220    }
221    Ok(())
222}