Skip to content

Commit

Permalink
Merge pull request SciML#22 from jClugstor/parser_improvements
Browse files Browse the repository at this point in the history
Parsing Improvements
  • Loading branch information
jClugstor authored Jul 22, 2024
2 parents 8a8ef35 + eadf96e commit bc03c37
Show file tree
Hide file tree
Showing 11 changed files with 1,073 additions and 451 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,16 @@ concurrency:
jobs:
tests:
name: "Tests"
strategy:
fail-fast: false
matrix:
group:
- Core
- Quality
version:
- '1'
uses: "SciML/.github/.github/workflows/tests.yml@v1"
with:
group: "${{ matrix.group }}"
secrets: "inherit"

8 changes: 5 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
name = "BaseModelica"
uuid = "a17d5099-185d-4ff5-b5d3-51aa4569e56d"
authors = ["jClugstor <[email protected]> and contributors"]
version = "1.0.0"
version = "1.1.0"

[deps]
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
ParserCombinator = "fae87a5f-d1ad-5cf0-8f61-c941e1580b46"

[compat]
Aqua = "0.8"
ModelingToolkit = "8.75, 9"
MLStyle = "0.4.17"
ModelingToolkit = "9"
ParserCombinator = "2"
SafeTestsets = "0.1"
Test = "1.10"
julia = "1.10"
ParserCombinator = "2"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor%27s%20Guide-blueviolet)](https://github.com/SciML/ColPrac)
[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle)


A parser for the [Base Modelica](https://github.com/modelica/ModelicaSpecification/tree/MCP/0031/RationaleMCP/0031) format. Contains utilities to parse Base Modelica model files in to Julia objects, and to convert Base Modelica models to [ModelingToolkit](https://docs.sciml.ai/ModelingToolkit/stable/) models.

So far only very simple Base Modelica models are supported. Only models with real parameters, real variables, and equations consisting of simple arithmetic equations and first order derivatives are supported. Support for the rest of the BaseModelica specification is planned to be added in the future.
So far only very simple Base Modelica models are supported. Only models with real parameters, real variables, and equations consisting of simple arithmetic equations and first order derivatives are supported. Support for the rest of the BaseModelica specification is planned to be added in the future.

## Installation

Expand All @@ -25,6 +24,7 @@ Pkg.add("BaseModelica");
```

# Example

A Base Modelica model is in the file `ExampleFirstOrder.mo`. Inside of the file is a Base Modelica model specifying a simple first order linear differential equation:

```
Expand Down
55 changes: 3 additions & 52 deletions src/BaseModelica.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,11 @@ module BaseModelica

using ModelingToolkit
using ParserCombinator

"""
Holds the name of the package, the models in the package, and eventually BaseModelica records.
"""
struct BaseModelicaPackage
name::Any
model::Any
end

"""
Represents a BaseModelica model.
"""
struct BaseModelicaModel
name::Any
description::Any
parameters::Any
variables::Any
equations::Any
initialequations::Any
end

struct BaseModelicaParameter
type::Any
name::Any
value::Any
description::Any
end

struct BaseModelicaVariable
type::Any
name::Any
input_or_output::Any
description::Any
end

struct BaseModelicaEquation
lhs::Any
rhs::Any
description::Any
end

struct BaseModelicaInitialEquation
lhs::Any
rhs::Any
description::Any
end

# needed to parse derivatives in equations correctly
@variables t
der = Differential(t)
using MLStyle

#Includes
include("parser.jl")
include("conversion.jl")
include("evaluator.jl")
"""
parse_basemodelica(filename::String)::ODESystem
Expand All @@ -69,7 +20,7 @@ parse_basemodelica("testfiles/NewtonCoolingBase.mo")
"""
function parse_basemodelica(filename::String)
package = parse_file(filename)
baseModelica_to_ModelingToolkit(package.model)
baseModelica_to_ModelingToolkit(package)
end

export parse_basemodelica
Expand Down
49 changes: 0 additions & 49 deletions src/conversion.jl

This file was deleted.

154 changes: 154 additions & 0 deletions src/evaluator.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# function to convert "AST" to ModelingToolkit
using ModelingToolkit: t_nounits as t, D_nounits as D

@data BaseModelicaRuntimeTypes begin
RTRecord()
RTClass()
RTFunction()
#built in classes
RTReal()
RTInteger()
RTBoolean()
RTString()
end

function eval_AST(expr::BaseModelicaExpr)
let f = eval_AST
@match expr begin
BaseModelicaNumber(val) => val
BaseModelicaFactor(base, exp) => (f(base))^f(exp)
BaseModelicaSum(left, right) => (f(left)) + (f(right))
BaseModelicaMinus(left, right) => f(left) - f(right)
BaseModelicaProd(left, right) => f(left) * f(right)
BaseModelicaDivide(left, right) => f(left) / f(right)
BaseModelicaNot(relation) => !(f(relation))
BaseModelicaAnd(left, right) => f(left) && f(right)
BaseModelicaOr(left, right) => f(left) || f(right)
BaseModelicaLEQ(left, right) => f(left) <= f(right)
BaseModelicaGEQ(left, right) => f(left) >= f(right)
BaseModelicaLessThan(left, right) => f(left) < f(right)
BaseModelicaGreaterThan(left, right) => f(left) > f(right)
BaseModelicaEQ(left, right) => f(left) == f(right)
BaseModelicaNEQ(left, right) => f(left) != f(right)
_ => nothing
end
end
end

include("maps.jl")

function eval_AST(eq::BaseModelicaInitialEquation)
inner_eq = eq.equation.equation
Dict(eval_AST(inner_eq.lhs) => eval_AST(inner_eq.rhs))
end

function eval_AST(eq::BaseModelicaAnyEquation)
equation = eval_AST(eq.equation)
description = eq.description
return equation
end

function eval_AST(eq::BaseModelicaSimpleEquation)
lhs = eval_AST(eq.lhs)
rhs = eval_AST(eq.rhs)
lhs ~ rhs
end

function eval_AST(component::BaseModelicaComponentClause)
#this mutates a dict
#place holder to get simple equations working
#also needs to account for "modifications"
#also doesn't handle constants yet
list = component.component_list
type_prefix = component.type_prefix.dpc
declaration = list[1].declaration
name = Symbol(declaration.ident[1].name)
if type_prefix == "parameter"
variable_map[name] = only(@parameters($name))
parameter_val_map[variable_map[name]] = declaration.modification[1].expr[1].val
return variable_map[name]
elseif isnothing(type_prefix)
variable_map[name] = only(@variables($name(t)))
end
end

function eval_AST(model::BaseModelicaModel)
class_specifier = model.long_class_specifier
model_name = class_specifier.name
description = class_specifier.description

composition = class_specifier.composition

components = composition.components
equations = composition.equations
initial_equations = composition.initial_equations

#vars = [eval_AST(comp) for comp in components if comp.type_prefix.dpc != "parameter"]
#pars = [eval_AST(comp) for comp in components if comp.type_prefix.dpc == "parameter"]

# this loop populates the variable_map
vars = Num[]
pars = Num[]
for comp in components
name = Symbol(comp.component_list[1].declaration.ident[1].name)

eval_AST(comp)

if comp.type_prefix.dpc == "parameter" || comp.type_prefix.dpc == "constant"
push!(pars, variable_map[name])
else
push!(vars, variable_map[name])
end
end

eqs = [eval_AST(eq) for eq in equations]

#vars_and_pars = merge(Dict(vars .=> vars), Dict(pars .=> pars))
#println(vars_and_pars)
#eqs = [substitute(x,vars_and_pars) for x in eqs]

init_eqs = [eval_AST(eq) for eq in initial_equations]
init_eqs_dict = Dict()

# quick and dumb kind of
for dictionary in init_eqs
for (key, value) in dictionary
init_eqs_dict[key] = value
end
end
for (key, value) in init_eqs_dict
init_eqs_dict[key] = substitute(value, parameter_val_map)
end

#vars,pars,eqs, init_eqs_dict

defaults = merge(init_eqs_dict, parameter_val_map)
@named model = ODESystem(eqs, t, vars, pars; defaults)
structural_simplify(model)
end

function eval_AST(package::BaseModelicaPackage)
model = package.model
eval_AST(model)
end

function eval_AST(function_args::BaseModelicaFunctionArgs)
args = function_args.args
eval_AST.([args...])
end

function eval_AST(function_call::BaseModelicaFunctionCall)
function_name = Symbol(function_call.func_name)
args = eval_AST(function_call.args)
function_map[function_name](args)
end

function eval_AST(comp_reference::BaseModelicaComponentReference)
#will need to eventually account for array references and dot references...
#for now only does direct references to variables
return variable_map[Symbol(comp_reference.ref_list[1].name)]
end

function baseModelica_to_ModelingToolkit(package::BaseModelicaPackage)
eval_AST(package)
end
14 changes: 14 additions & 0 deletions src/maps.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# holds functions predefined or defined in the Base Modelica Package
function_map = Dict(
:der => x -> D(x...),
:abs => x -> Base.abs(x...),
:sin => x -> Base.sin(x...)
)

# holds variables, populated by evaluating component_clause
variable_map = Dict()

# holds parameter values, default values
parameter_val_map = Dict()

initial_value_map = Dict()
Loading

0 comments on commit bc03c37

Please sign in to comment.