Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
john-hen committed May 17, 2020
0 parents commit 6f3c546
Show file tree
Hide file tree
Showing 31 changed files with 2,703 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/dev/
/docs/rendered/
/dist/
**/__pycache__
**/.pytest_cache
flake8.txt
*.lnk
Thumbs.db
35 changes: 35 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Python scripting interface for Comsol Multiphysics®

[Comsol][comsol] is a commercial software application that is widely
used in science and industry alike for research and development. It
excels in modeling almost any (multi-)physics problems by solving the
governing set of partial differential equations via the finite-element
method. It comes with a modern graphical user interface to set up
simulation models and can be scripted from Matlab® or via its native
Java API.

This library brings the dearly missing power of Python to the world
of Comsol — at least on Windows (for now). It leverages the universal
Python-to-Java bridge provided by [JPype][jpype] to access the native
API, and wraps it in a layer of pythonic ease-of-use. The Python
wrapper only covers common scripting tasks, such as loading a model
from a file, modifying some parameters, running the simulation, to
then evaluate the results. Though the full functionality is available
to those who dig down to the Java layer underneath.

Comsol models are marked by their `.mph` file extension, which stands
for multiphysics. Hence the name of this library. It is open-source
and in no way associated with Comsol Inc., the company that develops
and licenses the simulation software.

Find the [full documentation on Read-the-Docs][docs].


[comsol]: https://www.comsol.com
[jpype]: https://pypi.org/project/JPype1
[docs]: https://mph.readthedocs.io

[![version](https://img.shields.io/pypi/v/mph.svg)](https://pypi.python.org/pypi/mph)
[![downloads](https://pepy.tech/badge/mph)](https://pepy.tech/project/mph)
[![license](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![documentation](https://readthedocs.org/projects/mph/badge/?version=latest)](https://mph.readthedocs.io/en/latest/?badge=latest)
98 changes: 98 additions & 0 deletions demos/compact_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
Compacts Comsol models in the current folder.
Removes solution and mesh data and resets the modeling history.
Then saves the model file under its original name, effectively
compacting its size.
Processes all models it finds in the current folder. Optionally,
includes subfolders as well, if the user enters "all" after the
script starts.
"""
__license__ = 'MIT'


########################################
# Dependencies #
########################################
import mph
from pathlib import Path
from time import perf_counter as now


########################################
# Timer #
########################################
class Timer():
"""Convenience class for measuring and displaying elapsed time."""

def __init__(self, margin=4, padding=12):
self.t0 = None
self.margin = margin
self.padding = padding

def start(self, step):
"""Starts timing a step, displaying its name."""
print(' '*self.margin + f'{step:{self.padding}}', end='', flush=True)
self.t0 = now()

def cancel(self, reason=''):
"""Cancels timing the step, displaying the reason."""
print(reason, flush=True)

def stop(self):
"""Stops timing the step, displaying the elapsed time."""
elapsed = now() - self.t0
print(f'{elapsed:.1f} seconds', flush=True)


########################################
# Main #
########################################

# Display welcome message.
print('Compact Comsol models in the current folder.')

# Have user type "all" to indicate subfolders should be included.
print('Press Enter to start. Type "all" to include subfolders.')
if input() == 'all':
files = Path.cwd().rglob('*.mph')
else:
files = Path.cwd().glob('*.mph')

# Start Comsol client.
print('Running Comsol client on single processor core.')
client = mph.Client(cores=1)

# Loop over model files.
timer = Timer()
for file in files:

name = file.relative_to(Path.cwd())
print(f'{name}:')

timer.start('Loading')
try:
model = client.load(file)
timer.stop()
except Exception:
timer.cancel('Failed.')
continue

timer.start('Clearing')
model.clear()
timer.stop()

timer.start('Resetting')
try:
model.reset()
timer.stop()
except Exception:
timer.cancel('Failed.')

timer.start('Saving')
model.save()
timer.stop()

# Have user press Enter before the console window might close itself.
input('Press Enter to quit.')
19 changes: 19 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
API
---

Code documentation of the public application programming interface
provided by this library.

```eval_rst
.. currentmodule:: mph
.. autosummary::
:toctree: api
:nosignatures:
Model
Client
Server
backend
```
4 changes: 4 additions & 0 deletions docs/api/mph.Client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Client
======

.. autoclass:: mph.Client
4 changes: 4 additions & 0 deletions docs/api/mph.Model.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Model
=====

.. autoclass:: mph.Model
4 changes: 4 additions & 0 deletions docs/api/mph.Server.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Server
======

.. autoclass:: mph.Server
4 changes: 4 additions & 0 deletions docs/api/mph.backend.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
backend
=======

.. automodule:: mph.backend
Binary file added docs/capacitor.mph
Binary file not shown.
134 changes: 134 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""
Configuration file for rendering the documentation.
The files in this folder are used to render the documentation of this
package, from its source files, as a static web site. The renderer is
the documentation generator Sphinx. It is configured by this very
script and would be invoked on the command line via, on any operating
system, via `sphinx-build . rendered`. The static HTML then ends up in
the sub-folder `rendered`, where `index.html` is the start page.
The source files are the `.md` files here, where `index.md` maps to
the start page, as well as the documentation string in the package's
source code for the API documentation.
All text may use mark-up according to the CommonMark specification of
the Markdown syntax. The Sphinx extension `recommonmark` is used to
convert Markdown to reStructuredText, Sphinx's native input format.
"""
__license__ = 'MIT'


########################################
# Dependencies #
########################################

import sphinx_rtd_theme # Read-the-Docs theme
import recommonmark # Markdown extension
import recommonmark.transform # Markdown transformations
import commonmark # Markdown parser
import re # regular expressions
import sys # system specifics
from unittest.mock import MagicMock # mock imports
from pathlib import Path # file-system paths

extensions = [
'recommonmark', # Accept Markdown as input.
'sphinx.ext.autodoc', # Get documentation from doc-strings.
'sphinx.ext.autosummary', # Create summaries automatically.
'sphinx.ext.viewcode', # Add links to highlighted source code.
'sphinx.ext.mathjax', # Render math via JavaScript.
]

# Add the project folder to the module search path.
main = Path(__file__).absolute().parent.parent
sys.path.insert(0, str(main))

# Mock external dependencies so they are not required at build time.
autodoc_mock_imports = ['jpype', 'numpy']
for package in ('jpype', 'jpype.types', 'jpype.imports', 'numpy'):
sys.modules[package] = MagicMock()

# Import package to make meta data available.
import mph as package


########################################
# Customization #
########################################

def convert(text):
"""
Converts text from Markdown to reStructuredText syntax.
Also converts the Unicode bullet character (•) to a standard
list-item marker (*) so that the CommomMark parser recognizes it
as such — which it regrettably doesn't.
"""
text = re.sub(r'^([ \t]*)•', r'\1*', text, flags=re.MULTILINE)
ast = commonmark.Parser().parse(text)
rst = commonmark.ReStructuredTextRenderer().render(ast)
return rst


def docstrings(app, what, name, obj, options, lines):
"""Converts Markdown in doc-strings to reStructuredText."""
md = '\n'.join(lines)
rst = convert(md)
lines.clear()
lines += rst.splitlines()


def setup(app):
"""Sets up event hooks for customized text processing."""
app.connect('autodoc-process-docstring', docstrings)
app.add_config_value('recommonmark_config', {
'auto_toc_tree_section': 'Contents',
'enable_math': True,
'enable_inline_math': True,
'enable_eval_rst': True,
}, True)
app.add_transform(recommonmark.transform.AutoStructify)


########################################
# Configuration #
########################################

# Meta information
project = package.__title__
version = package.__version__
date = package.__date__
author = package.__author__
copyright = package.__copyright__
license = package.__license__

# Source parsing
master_doc = 'index' # start page
source_suffix = ['.md', '.rst'] # valid source-file suffixes
exclude_patterns = [] # files and folders to ignore
language = None # language for auto-generated content
todo_include_todos = False # Include "todo" and "todoList"?
nitpicky = True # Warn about missing references?

# Code documentation
add_module_names = False # Don't precede members with module name.
autodoc_default_options = {
'members': True, # Include module/class members.
'member-order': 'bysource', # Order members as in source file.
}

# HTML rendering
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme_options = {}
templates_path = ['layout'] # layout tweaks
html_static_path = ['style'] # style tweaks
html_css_files = ['custom.css'] # style sheets
pygments_style = 'trac' # syntax highlighting style
html_use_index = False # Create document index?
html_copy_source = False # Copy documentation source files?
html_show_copyright = False # Show copyright notice in footer?
html_show_sphinx = False # Show Sphinx blurb in footer?
html_favicon = None # browser icon
html_logo = None # project logo
38 changes: 38 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
MPh
===

Python scripting interface for Comsol Multiphysics®.

[Comsol][comsol] is a commercial software application that is widely
used in science and industry alike for research and development. It
excels in modeling almost any (multi-)physics problems by solving the
governing set of partial differential equations via the finite-element
method. It comes with a modern graphical user interface to set up
simulation models and can be scripted from Matlab® or via its native
Java API.

This library brings the dearly missing power of Python to the world
of Comsol — at least on Windows (for now). It leverages the universal
Python-to-Java bridge provided by [JPype][jpype] to access the native
API, and wraps it in a layer of pythonic ease-of-use. The Python
wrapper only covers common scripting tasks, such as loading a model
from a file, modifying some parameters, running the simulation, to
then evaluate the results. Though the full functionality is available
to those who dig down to the Java layer underneath.

Comsol models are marked by their `.mph` file extension, which stands
for multiphysics. Hence the name of this library. It is open-source
and in no way associated with Comsol Inc., the company that develops
and licenses the simulation software.


[comsol]: https://www.comsol.com
[jpype]: https://pypi.org/project/JPype1


Contents
--------
* [Installation](installation.md)
* [Tutorial](tutorial.md)
* [Limitations](limitations.md)
* [API](api.md)
23 changes: 23 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Installation
------------

MPh is [available on PyPI][dist] and can be readily installed via `pip`:
```none
pip install mph
```

Add `--user` to the above command to make it a per-user installation,
instead of system-wide, which may or may not be preferable. Run the
same command with `install` replaced by `uninstall` in order to remove
the library from your system.

Requires [JPype][jpype] for the bridge from Python to [Comsol's
Java API][java] and [NumPy][numpy] for returning (fast) numerical arrays.
`pip` makes sure the two Python dependencies are installed and adds them
if missing. Comsol, obviously, you need to license and install yourself.


[dist]: https://pypi.python.org/pypi/mph
[jpype]: https://jpype.readthedocs.io
[java]: https://www.comsol.com/blogs/automate-modeling-tasks-comsol-api-use-java/
[numpy]: https://numpy.org

0 comments on commit 6f3c546

Please sign in to comment.