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}