aboutsummaryrefslogtreecommitdiffstats
path: root/src/transpile_python.rs
blob: f38f699c8d81ab96ec56bc3366a107d3f7996ebd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

extern crate modelica_parser;

use self::modelica_parser::*;
use errors::Result;

pub trait TranspilePython {
    fn transpile_python(&self) -> Result<String>;
}

pub trait TranspilePythonODE {
    fn transpile_python_ode(&self) -> Result<String>;
}


impl TranspilePython for ModelicaModel {
    fn transpile_python(&self) -> Result<String> {
        let mut params = vec![];
        let mut constants = vec![];
        for (c, e) in self.get_constant_vars() {
            if let Some(v) = e {
                constants.push(format!("{} = {}", c, try!(v.transpile_python())));
            } else {
                params.push(c);
            }
        }
        // HashMaps are unsorted, so we need to re-sort here
        constants.sort();
        params.sort();
        let mut binds = vec![];
        let mut outputs = vec![];
        for eq in self.equations.iter() {
            if let Expr::Ident(ref symb) = eq.lhs {
                binds.push(format!("{} = {}",
                    symb,
                    try!(eq.rhs.transpile_python())));
                outputs.push(symb.to_string());

            } else {
                bail!("Expected an identifier on LHS (in this partial implementation)")
            }
        }
        let mut args: Vec<String> = self.get_free_vars().iter().map(|s| s.clone()).collect();
        args.sort();
        args.extend(params);
        Ok(format!(
r#"def {slug}({args}):
    """{description}

    Args:
        {param_doc}

    Returns array of:
        {ret_doc}
    """
    {constants}
    {binds}
    return ({outputs})
"#,
                   description = self.description.clone().unwrap_or("(undocumented)".to_string()),
                   param_doc = args.join("\n        "), // whitespace sensitive
                   ret_doc = outputs.join("\n        "), // whitespace sensitive
                   slug = self.name,
                   args = args.join(", "),
                   constants = constants.join("\n    "), // whitespace sensitive
                   binds = binds.join("\n    "), // whitespace sensitive
                   outputs = outputs.join(", ")))
    }

}

impl TranspilePythonODE for ModelicaModel {

    fn transpile_python_ode(&self) -> Result<String> {
        let mut params = vec![];
        let mut constants = vec![];
        for (c, e) in self.get_constant_vars() {
            if let Some(v) = e {
                constants.push(format!("{} = {}", c, try!(v.transpile_python())));
            } else {
                params.push(c);
            }
        }
        // HashMaps are unsorted, so we need to re-sort here
        constants.sort();
        params.sort();
        let mut exprs = vec![];
        for eq in self.equations.iter() {
            if let Expr::Der(ref der_of) = eq.lhs {
                if let &Expr::Ident(_) = der_of.as_ref() {
                    // Ok
                } else {
                    bail!("Non-trivial derivatives not supported (aka, of non-variable expressions)");
                }
                exprs.push(try!(eq.rhs.transpile_python()));
            } else {
                bail!("Not a simple set of ODEs (aka, all derivatives on LHS of equations)");
            }
        }
        let mut args: Vec<String> = self.get_free_vars().iter().map(|s| s.clone()).collect();
        args.sort();
        Ok(format!(
r#"def {slug}_ode({params}):
    def f({args}):
        {constants}
        return (
            {expressions}
        )
    return f
"#,
                   slug = self.name,
                   params = params.join(", "),
                   args = args.join(", "),
                   constants = constants.join("\n        "), // NB: whitespace
                   expressions = exprs.join(",\n            "))) // NB: whitespace
    }
}

impl TranspilePython for Expr {
    fn transpile_python(&self) -> Result<String> {
        use modelica_parser::Expr::*;
        use modelica_parser::BinOperator::*;
        match *self {
            Integer(e) => Ok(format!("{}", e)),
            Float(e) => Ok(format!("{:e}", e)),
            Boolean(true) => Ok(format!("True")),
            Boolean(false) => Ok(format!("False")),
            StringLiteral(ref s) => Ok(format!("\"{}\"", s)),
            Ident(ref e) => Ok(format!("{}", e)),
            Der(_) => unimplemented!(),
            Sign(_) => unimplemented!(),
            MathUnaryExpr(func, ref e) => Ok(format!("{:?}({})", func, try!(e.transpile_python()))),
            BinExpr(op, ref l, ref r) => {
                // Have override for Python's exponentiation
                let op_str = match op {
                    Multiply => "*",
                    Divide => "/",
                    Exponentiate => "**",
                    Add => "+",
                    Subtract => "-",
                };
                Ok(format!("({} {} {})", try!(l.transpile_python()), op_str, try!(r.transpile_python())))
            }
            Array(_) => unimplemented!(),
        }
    }
}