rooc/pipe/
pipe_wasm_runner.rs

1#[allow(unused_imports)]
2use crate::prelude::*;
3use serde::{Deserialize, Serialize};
4
5#[cfg(target_arch = "wasm32")]
6use crate::pipe::run_pipe;
7#[cfg(target_arch = "wasm32")]
8use crate::runtime_builtin::JsFunction;
9use crate::{IntOrBoolValue, LinearModel, LpSolution, MILPValue};
10#[cfg(target_arch = "wasm32")]
11use {
12    crate::parser::model_transformer::Model,
13    crate::parser::pre_model::PreModel,
14    crate::pipe::pipe_definitions::{PipeDataType, PipeError, Pipeable, PipeableData},
15    crate::pipe::pipe_executors::{
16        AutoSolverPipe, BinarySolverPipe, CompilerPipe, IntegerBinarySolverPipe, LinearModelPipe,
17        MILPSolverPipe, ModelPipe, Pipes, PreModelPipe, RealSolver, StandardLinearModelPipe,
18        StepByStepSimplexPipe, TableauPipe,
19    },
20    crate::pipe::PipeContext,
21    crate::solvers::{OptimalTableau, OptimalTableauWithSteps, Tableau},
22    crate::transformers::StandardLinearModel,
23    crate::RoocParser,
24    crate::{Constant, Primitive},
25};
26
27#[cfg(target_arch = "wasm32")]
28#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
29pub struct WasmPipeRunner {
30    pipes: Vec<Box<dyn Pipeable>>,
31}
32
33#[cfg(target_arch = "wasm32")]
34#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
35pub struct JsPipable {
36    function: Function,
37}
38#[cfg(target_arch = "wasm32")]
39#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
40impl JsPipable {
41    pub fn new_wasm(function: Function) -> JsPipable {
42        JsPipable { function }
43    }
44}
45
46#[derive(Serialize, Deserialize)]
47#[serde(tag = "type", content = "value")]
48enum JsPipableData {
49    String(String),
50    LinearModel(LinearModel),
51    BinarySolution(LpSolution<bool>),
52    IntegerBinarySolution(LpSolution<IntOrBoolValue>),
53    RealSolution(LpSolution<f64>),
54    MILPSolution(LpSolution<MILPValue>),
55}
56
57#[cfg(target_arch = "wasm32")]
58impl Pipeable for JsPipable {
59    fn pipe(
60        &self,
61        data: &mut PipeableData,
62        _pipe_context: &PipeContext,
63    ) -> Result<PipeableData, PipeError> {
64        let js_pipable = match data {
65            PipeableData::String(s) => JsPipableData::String(s.clone()),
66            PipeableData::LinearModel(lm) => JsPipableData::LinearModel(lm.clone()),
67            PipeableData::BinarySolution(sol) => JsPipableData::BinarySolution(sol.clone()),
68            PipeableData::IntegerBinarySolution(sol) => {
69                JsPipableData::IntegerBinarySolution(sol.clone())
70            }
71            PipeableData::RealSolution(sol) => JsPipableData::RealSolution(sol.clone()),
72            PipeableData::MILPSolution(sol) => JsPipableData::MILPSolution(sol.clone()),
73            _ => return Err(PipeError::Other("JsPipable data must be one of: String, LinearModel, BinarySolution, IntegerBinarySolution, RealSolution, MILPSolution".to_string()))
74        };
75        let js_pipable =
76            serialize_json_compatible(&js_pipable).map_err(|e| PipeError::Other(e.to_string()))?;
77        let js_args = js_sys::Array::new();
78        js_args.push(&js_pipable);
79        let result = self.function.apply(&JsValue::NULL, &js_args).map_err(|e| {
80            PipeError::Other(
81                e.as_string()
82                    .unwrap_or("Error thrown in function".to_string()),
83            )
84        })?;
85        let result: JsPipableData =
86            serde_wasm_bindgen::from_value(result).map_err(|e| PipeError::Other(e.to_string()))?;
87        let pipe_result = match result {
88            JsPipableData::String(s) => PipeableData::String(s),
89            JsPipableData::LinearModel(lm) => PipeableData::LinearModel(lm),
90            JsPipableData::BinarySolution(sol) => PipeableData::BinarySolution(sol),
91            JsPipableData::IntegerBinarySolution(sol) => PipeableData::IntegerBinarySolution(sol),
92            JsPipableData::RealSolution(sol) => PipeableData::RealSolution(sol),
93            JsPipableData::MILPSolution(sol) => PipeableData::MILPSolution(sol),
94        };
95        Ok(pipe_result)
96    }
97}
98
99#[cfg(target_arch = "wasm32")]
100#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
101impl WasmPipeRunner {
102    pub fn new_wasm() -> WasmPipeRunner {
103        WasmPipeRunner { pipes: vec![] }
104    }
105
106    pub fn add_step_by_name(&mut self, step: Pipes) {
107        let step: Box<dyn Pipeable> = match step {
108            Pipes::CompilerPipe => Box::new(CompilerPipe::new()),
109            Pipes::PreModelPipe => Box::new(PreModelPipe::new()),
110            Pipes::ModelPipe => Box::new(ModelPipe::new()),
111            Pipes::LinearModelPipe => Box::new(LinearModelPipe::new()),
112            Pipes::StandardLinearModelPipe => Box::new(StandardLinearModelPipe::new()),
113            Pipes::TableauPipe => Box::new(TableauPipe::new()),
114            Pipes::RealPipe => Box::new(RealSolver::new()),
115            Pipes::StepByStepSimplexPipe => Box::new(StepByStepSimplexPipe::new()),
116            Pipes::BinarySolverPipe => Box::new(BinarySolverPipe::new()),
117            Pipes::IntegerBinarySolverPipe => Box::new(IntegerBinarySolverPipe::new()),
118            Pipes::MILPSolverPipe => Box::new(MILPSolverPipe::new()),
119            Pipes::AutoSolverPipe => Box::new(AutoSolverPipe::new()),
120        };
121        self.pipes.push(step);
122    }
123
124    pub fn add_step_with_fn(&mut self, function: JsPipable) {
125        self.pipes.push(Box::new(function));
126    }
127
128    pub fn wasm_run_from_string(
129        &self,
130        data: String,
131        constants: JsValue,
132        fns: Vec<JsFunction>,
133    ) -> Result<Vec<WasmPipableData>, WasmPipeError> {
134        let data = PipeableData::String(data);
135        let constants: Vec<(String, Primitive)> = serde_wasm_bindgen::from_value(constants)
136            .map_err(|e| WasmPipeError::new(PipeError::Other(e.to_string()), vec![]))?;
137        let constants = constants
138            .into_iter()
139            .map(|v| Constant::from_primitive(&v.0, v.1))
140            .collect();
141        let fns = js_value_to_fns_map(fns);
142        match run_pipe(&self.pipes, data, &PipeContext::new(constants, &fns)) {
143            Ok(results) => Ok(results.into_iter().map(WasmPipableData::new).collect()),
144            Err((e, results)) => {
145                let results: Vec<WasmPipableData> =
146                    results.into_iter().map(WasmPipableData::new).collect();
147                Err(WasmPipeError::new(e, results))
148            }
149        }
150    }
151}
152
153#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
154#[cfg(target_arch = "wasm32")]
155pub struct WasmPipeError {
156    error: PipeError,
157    context: Vec<WasmPipableData>,
158}
159
160#[cfg(target_arch = "wasm32")]
161impl WasmPipeError {
162    pub fn new(error: PipeError, context: Vec<WasmPipableData>) -> WasmPipeError {
163        WasmPipeError { error, context }
164    }
165}
166
167#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
168#[cfg(target_arch = "wasm32")]
169impl WasmPipeError {
170    pub fn wasm_get_error(&self) -> String {
171        self.error.to_string()
172    }
173    pub fn wasm_get_context(&self) -> Vec<WasmPipableData> {
174        self.context.clone()
175    }
176    pub fn wasm_to_context(self) -> Vec<WasmPipableData> {
177        self.context
178    }
179}
180
181#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
182#[cfg(target_arch = "wasm32")]
183#[derive(Debug, Clone)]
184pub struct WasmPipableData {
185    data: PipeableData,
186}
187
188#[cfg(target_arch = "wasm32")]
189impl WasmPipableData {
190    pub fn new(data: PipeableData) -> WasmPipableData {
191        WasmPipableData { data }
192    }
193}
194
195#[cfg(target_arch = "wasm32")]
196impl From<WasmPipableData> for PipeableData {
197    fn from(data: WasmPipableData) -> Self {
198        data.data
199    }
200}
201#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
202#[allow(clippy::wrong_self_convention)]
203#[cfg(target_arch = "wasm32")]
204impl WasmPipableData {
205    pub fn wasm_get_type(&self) -> PipeDataType {
206        self.data.get_type()
207    }
208
209    //TODO is there a better way to do this instead of making singular functions for each type?
210    pub fn to_string_data(self) -> Result<String, JsValue> {
211        self.data
212            .to_string_data()
213            .map_err(|e| JsValue::from_str(&e.to_string()))
214    }
215    pub fn to_parser(self) -> Result<RoocParser, JsValue> {
216        self.data
217            .to_parser()
218            .map_err(|e| JsValue::from_str(&e.to_string()))
219    }
220    pub fn to_pre_model(self) -> Result<PreModel, JsValue> {
221        self.data
222            .to_pre_model()
223            .map_err(|e| JsValue::from_str(&e.to_string()))
224    }
225    pub fn to_model(self) -> Result<Model, JsValue> {
226        self.data
227            .to_model()
228            .map_err(|e| JsValue::from_str(&e.to_string()))
229    }
230    pub fn to_linear_model(self) -> Result<LinearModel, JsValue> {
231        self.data
232            .to_linear_model()
233            .map_err(|e| JsValue::from_str(&e.to_string()))
234    }
235    pub fn to_standard_linear_model(self) -> Result<StandardLinearModel, JsValue> {
236        self.data
237            .to_standard_linear_model()
238            .map_err(|e| JsValue::from_str(&e.to_string()))
239    }
240    pub fn to_tableau(self) -> Result<Tableau, JsValue> {
241        self.data
242            .to_tableau()
243            .map_err(|e| JsValue::from_str(&e.to_string()))
244    }
245    pub fn to_optimal_tableau(self) -> Result<OptimalTableau, JsValue> {
246        self.data
247            .to_optimal_tableau()
248            .map_err(|e| JsValue::from_str(&e.to_string()))
249    }
250    pub fn to_optimal_tableau_with_steps(self) -> Result<OptimalTableauWithSteps, JsValue> {
251        self.data
252            .to_optimal_tableau_with_steps()
253            .map_err(|e| JsValue::from_str(&e.to_string()))
254    }
255    pub fn to_binary_solution(self) -> Result<JsValue, JsValue> {
256        self.data
257            .to_binary_solution()
258            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
259            .map_err(|e| JsValue::from_str(&e.to_string()))
260    }
261    pub fn to_integer_binary_solution(self) -> Result<JsValue, JsValue> {
262        self.data
263            .to_integer_binary_solution()
264            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
265            .map_err(|e| JsValue::from_str(&e.to_string()))
266    }
267
268    pub fn to_real_solution(self) -> Result<JsValue, JsValue> {
269        self.data
270            .to_real_solution()
271            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
272            .map_err(|e| JsValue::from_str(&e.to_string()))
273    }
274
275    pub fn to_milp_solution(self) -> Result<JsValue, JsValue> {
276        self.data
277            .to_milp_solution()
278            .map(|s| serde_wasm_bindgen::to_value(&s).unwrap())
279            .map_err(|e| JsValue::from_str(&e.to_string()))
280    }
281}