Skip to main content

mphkit

PyPI Python License COMSOL

Helpers on top of MPh for COMSOL in Python: build geometry, select by location, insert library materials, check a model before solving, summarize and compare models' settings, and read results as numbers or pictures. Selections made by location, not entity number, keep working when the geometry changes.

Not affiliated with COMSOL AB.

Example

import mph
import mphkit as mk

client = mph.start()
model = client.create('demo')
geom = mk.geometry(model, 3, length_unit='mm')

plate = mk.block(geom, (100, 100, 10), name='plate')
hole = mk.cylinder(geom, 5, 10, (50, 50, 0))
mk.difference(geom, plate, [hole])
model.build(geom)

bottom = mk.sel.box(geom, 'boundary', z=0)     # faces on the plane z = 0

# plain MPh from here on
physics = (model/'physics').create('HeatTransfer', geom)
physics.create('TemperatureBoundary', 2).select(bottom)  # 2: boundaries in 3D
model.save('demo.mph')

Helpers take MPh Nodes (mk.geometry the model, mk.set also Java objects), and those that create something return one, so mphkit and MPh mix freely. Physics, mesh and study stay plain MPh (or the COMSOL Java API through node.java); mphkit looks up the COMSOL names they need and mk.describe reads them.

print(mphkit.__doc__) shows the workflow as one script, the rules and an index of every helper; help(mk.<name>) has the details of one, help(mk.sel) those of the selections. Point an AI assistant to print(mphkit.__doc__) first.

Requirements

  • COMSOL Multiphysics with a license, installed where MPh can find it (see the MPh documentation).
  • Python 3.10 or newer, MPh 1.4 or newer (below 2: mphkit uses some of its internals).
  • Importing CAD files (mk.import_ with STEP, IGES, ...) needs a license for CAD import (CAD Import Module, Design Module or a LiveLink). Everything else needs COMSOL only.

mphkit is developed and tested with COMSOL 6.4 and MPh 1.4. Feature types, property names and selection behavior can differ between COMSOL versions, so other versions may need adjustments; reports are welcome. Checked on macOS and Windows; it should run wherever MPh runs. The name lookups read COMSOL's code-completion data in the installation (data/completion), which COMSOL does not document; they were checked with COMSOL 6.4 on macOS and Windows. A missing or changed catalogue raises an error rather than giving wrong names.

Installation

pip install mphkit

or uv add mphkit in a uv project.

What changed between versions is listed in CHANGELOG.md.

What it covers

Geometry, in 3D, 2D and in work planes:

mk.block(geom, (10, 10, 5)); mk.cylinder(geom, r, h, pos); mk.sphere(geom, r)
mk.union(geom, [a, b]); mk.difference(geom, a, [b]); mk.intersection(geom, [a, b])
mk.move(geom, part, (10, 0, 0)); mk.rotate(geom, part, 90, axis='z')
mk.mirror(geom, part, (1, 0, 0)); mk.array(geom, part, size=(5, 5, 1), displ=(10, 10, 0))
mk.fillet(geom, part, 0.5); mk.chamfer(geom, part, 0.5)     # all edges, or a selection
mk.partition(geom, part, tool); mk.delete(geom, part)
plane = mk.workplane(geom, quickz=0)
mk.circle(plane, 2); mk.extrude(geom, plane, 5); mk.revolve(geom, plane)
mk.import_(geom, 'part.step')
mk.feature(geom, 'AnyType', ...)   # any other geometry feature
mk.geometry(model, 2, axisymmetric=True)  # r-z half plane: x is r, y is z

Selections by location (mk.sel), usable in physics, materials and mesh:

mk.sel.box(geom, 'boundary', z=0)                 # a range per axis, or a value
mk.sel.ball(geom, 'domain', center, r); mk.sel.cylinder(...); mk.sel.disk(...)
mk.sel.union(geom, 'boundary', [a, b])            # also intersection, difference, complement
mk.sel.adjacent(geom, domains)                    # boundaries around a domain selection
mk.sel.result(geom, feature, 'domain')            # what a feature produced
holes = mk.sel.cumulative(geom, 'holes', 'domain', create=True)
mk.cylinder(geom, 1, 5, pos, contributeto=holes)  # collect from several features

where='geometry' makes a selection inside the geometry sequence instead, for use as input of a later operation, e.g. mk.sel.box(geom, 'object', x=(20, 40), where='geometry') to pick whole objects for mk.delete. In a work plane, selections can pick single corners or edges, e.g. to fillet one corner: mk.fillet(plane, mk.sel.box(plane, 'point', x=1, y=1), 0.3).

Queries on the plate from the Example section return plain Python values and leave nothing in the model:

mk.sel.entities(geom, bottom)            # [3]
mk.sel.find(geom, 'boundary', x=0)       # [1]: the face at x = 0
mk.measure(geom, 'domain')               # 99216.5: volume, approximate where curved
mk.bounding_box(geom, 'boundary', 3)     # {'x': (0.0, 100.0), 'y': ..., 'z': (0.0, 0.0)}
mk.summary(geom)                         # counts, voids, bounding box, unit
mk.sel.neighbors(geom, 'domain', boundary=3)  # [1]: the domain beside it
mk.coordinates(geom, 'boundary', 3)      # {1: (0.0, 0.0, 0.0), 3: (0.0, 100.0, 0.0), ...}

Before solving, a check of what COMSOL would get wrong silently or vaguely (wrong units, domains without material, conditions that apply nowhere, no mesh, physics no study solves):

problems = [p for p in mk.check(model) if p['severity'] == 'warning']

Before a long solve, its size; while it runs, its progress, read from another process (an agent runs it in the background):

mk.problem_size(model)        # degrees of freedom, solver, mesh elements, memory and cores
mk.log_progress('solve.log')  # in the solving script, before loading the model
mk.progress('/abs/path/solve.log')  # elsewhere: percent, task, memory, time steps, alive, CPU

mk.problem_size compiles the equations without solving (it needs a built mesh) and does not predict memory or time; a direct solver needs far more memory than an iterative one. mk.progress reads COMSOL's progress log and the operating system and judges nothing. help(mk.progress) has a script that starts a solve in the background and how to stop it: on macOS (Linux not tried) end its Python process only, os.kill(pid, signal.SIGTERM); on Windows every process mk.progress lists.

A model rebuilt by a script can be checked against the one made in the COMSOL Desktop (old, e.g. loaded with client.load('old.mph')): mk.describe returns a model's settings as plain values, with selections described by the location of their entities, so nodes can be matched by type and place, whatever their tags and order, and mk.compare lists what differs:

old_settings = mk.describe(old)    # settings that differ from the defaults, by node
new_settings = mk.describe(model)  # JSON-ready, e.g. to keep with the script
differences = mk.compare(old_settings, new_settings)  # [] if all alike

Results of the solved example script, over entities or at points, in SI units unless unit is given; they leave nothing in the model either:

mk.integral(geom, 'boundary', 'ht.ntflux', selections['hot end'], unit='W')  # -2.74: flows in
mk.average(geom, 'domain', 'T', unit='degC')                   # 90.2
mk.minimum(geom, 'domain', 'T', unit='degC', position=True)    # (83.4, array([88.0, 20.0, ...])): a hole wall
mk.value(geom, 'T', [(50, 20, 2.5), (100, 20, 2.5)], unit='degC')  # array([89.8, 84.1])

ht.ntflux is the flux out of the domain. With several solutions, pass dataset= (or the study); with time steps or a sweep stored as steps, step=: a position (step=10 is the tenth step; a warning tells when another step has t = 10 s) or a value (step={'t': 10}). A unit that does not fit, a point outside the geometry or a geometry changed since the solve and not built again raise instead of giving a wrong number; solve again after any change, as a geometry changed and then built and meshed again is read with the old solution. Global values come from MPh: model.evaluate('expression', 'unit'), which reads only the last value of a sweep stored as an outer loop unless given the sweep's dataset.

Parametric sweeps that COMSOL stores as an outer loop (around a time-dependent or eigenvalue study or several frequencies, or over geometry or mesh parameters, or COMSOL's Material and Function Sweeps) take outer=, e.g. mk.average(geom, 'domain', 'T', unit='degC', outer='all', step='last') for one value per parameter value, or outer={'Th': '200[degC]'} for one; mk.outer_values(geom) tells whether a sweep is one (and lists the values, in SI units), and mk.step_values(geom) lists the steps' values, eigenfrequencies too ('freq', in Hz).

Pictures, written to a file:

mk.image(geom, 'geom.png')                              # the geometry
mk.image(geom, 'selection.png', selection, labels=True) # with numbers
mk.image(geom, 'mesh.png', mesh=True)                   # element quality
mk.plot(geom, 'T', 'T.png', unit='degC')                # a solved result
mk.plot(geom, 'T', 'mid.png', unit='degC', z=2.5, view='top')  # a slice from above

mk.plot also draws a selection only, deformed shapes (deform=True), fixed colours (color_range=, color_table=) and the values of a sweep, one picture each (mk.plot(geom, 'T', 'T_{outer}.png', outer='all', step='last')); like the other helpers it leaves nothing in the model.

Mesh quality in numbers, after model.mesh() (skewness by default, 1 is best):

mk.mesh_quality(geom)                  # lowest (e.g. 0.065), the 5 worst elements and where, a histogram, COMSOL's messages
mk.mesh_quality(geom, 'boundary', 3)   # the surface elements of boundary 3
mk.mesh_quality(geom, quality='volcircum')  # another of COMSOL's quality measures

The messages are what COMSOL reported when building the mesh, e.g. an edge much shorter than the element size, with the entities concerned.

COMSOL's names, looked up instead of guessed (search first, the full lists are long), here on the example script's model with heat = model/'physics'/'heat', mesh = model/'meshes'/'mesh' and study = model/'studies'/'static':

mk.physics_types(geom, search='heat')         # 'HeatTransfer', ...
mk.feature_types(heat, search='convective')   # 'ConvectiveOutflow', then 'HeatFluxBoundary' ({'boundary': 2}) via a choice
mk.feature_types(mesh)                        # 'FreeTet', 'Size', ...: what mesh.create() takes
mk.feature_types(study, search='time')        # ..., 'Transient', ...: by name first
mk.properties(heat, 'HeatFluxBoundary')       # descriptions, defaults, choices
mk.properties(heat/'cooling', search='flux')  # and current values
mk.variables(heat, search='heat flux')        # 'ht.ntflux' [W/m^2], on boundaries

Physics features come with the level numbers create() takes. mk.properties also takes geometry, mesh and study features and materials, or a type to create there, e.g. mk.properties(mesh, 'FreeTet').

Materials from COMSOL's libraries, instead of typing property values:

mk.materials(search='structural steel')             # names, groups, properties
steel = mk.material(geom, 'Structural steel')       # background first: all domains
water = mk.material(geom, 'Water, liquid', channel) # later ones take a selection

mk.materials() lists the basic library and a search looks in all of them; mk.material(..., library='acdc') takes the others. Each domain takes the material added last among those that select it.

And a few helpers outside geometry:

mk.coordinate_system(geom, 'PML', selection=layer)   # e.g. perfectly matched layers
mk.set(mesh_size, hmax=0.5, hgrad=2)   # any node or Java object; converts ints and lists

examples/plate_with_holes.py is a complete script, from geometry to solved results: heat conduction in a plate with a row of cooling holes, for one or more holes.

Limitations

  • Physics, mesh and study setup, and plots beyond mk.plot (arrows, streamlines, graphs, animations), stay plain MPh; mphkit looks up the names they need, checks them before solving and mk.describe reads them.
  • A parametric sweep that changes the geometry is read over selection nodes or all entities, each value in its own geometry, and not drawn; a box at fixed coordinates picks what lies there in each value. With physics on part of the geometry and the default mesh, the same holds for any sweep stored as an outer loop except material sweeps. A change that keeps every vertex but numbers the entities otherwise goes unnoticed. Batch and cluster sweeps, optimization studies and time-dependent studies ended by a stop condition were not tried.
  • mk.progress and mk.problem_size read memory (and mk.progress CPU) from macOS and Windows; on Linux some of them are None (not tried there).
  • No named helpers yet for geometry parts (PartInstance), sweeps, cones and the other remaining primitives, virtual operations or repair. They work through mk.feature(geom, 'Sweep', ...), which handles arguments like the named helpers (expressions, lists, nodes as inputs).

Development

uv run pytest

The tests start COMSOL and build real models. Without a CAD import license, the CAD import test is skipped. uv run pytest -m "not comsol" runs only the tests that need no COMSOL, in seconds.

License

MIT, see LICENSE.

Metadata

Release files for mphkit 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mphkit 0.4.0
File Size Uploaded
mphkit-0.4.0.tar.gz 201.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mphkit 0.4.0
File Interpreter ABI Platform
mphkit-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 417.2 kB

Release files / mphkit-0.4.0.tar.gz

Download URL mphkit-0.4.0.tar.gz
Size 201.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e7e3dc46e9053802e7174a1a02f8b8fc6f00131da0ea0c88f4b0787542fcc54c
BLAKE2b-256 checksum
How to use checksums
7e2b2ea7840c67464c0f2c769a387af936c85323c8254359b1298e97203aefca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release files / mphkit-0.4.0-py3-none-any.whl

Download URL mphkit-0.4.0-py3-none-any.whl
Size 215.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3dfb12743ed46920b50d25f22fdc29c38df0f8a94b2690af0c4b1e7601898e84
BLAKE2b-256 checksum
How to use checksums
391ee5954f8d1f5d905c465ee5536490598af1098293638f0fd52ad896c31ad4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page