Skip to content

Commit 8d10eac

Browse files
Merge branch 'master' into dependabot/npm_and_yarn/gui-js/axios-1.8.2
2 parents a84637f + d7d9c98 commit 8d10eac

File tree

6 files changed

+162
-60
lines changed

6 files changed

+162
-60
lines changed

Makefile

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ EXES=gui-tk/minsky$(EXE)
187187
endif
188188

189189

190-
DYLIBS=libminsky.$(DL) libminskyEngine.$(DL) libcivita.$(DL)
190+
DYLIBS=libminsky.$(DL) libminskyEngine.$(DL) libcivita.$(DL)
191191
MINSKYLIBS=-lminsky -lminskyEngine -lcivita
192192

193193

@@ -272,6 +272,9 @@ ifdef MXE
272272
BOOST_EXT=-mt-x64
273273
EXE=.exe
274274
DL=dll
275+
FLAGS+=-DMXE
276+
PYMINSKY=gui-js/dynamic_libraries/pyminsky.pyd
277+
PYTHONCAPI=ecolab/classdesc/pythonCAPI.o # extra python.lib shims required on Windows
275278
FLAGS+=-D_WIN32 -DUSE_UNROLLED -Wa,-mbig-obj
276279
# DLLS that need to be copied into the binary directory
277280
MXE_DLLS=libboost_filesystem-mt-x64 libboost_thread-mt-x64 \
@@ -288,6 +291,7 @@ DLLS=$(wildcard $(MXE_DLLS:%=$(BINDIR)/%*.dll))
288291
else
289292
EXE=
290293
DL=so
294+
PYMINSKY=pyminsky.so
291295
BOOST_EXT=
292296
# try to autonomously figure out which boost extension we should be using
293297
ifeq ($(shell if $(CPLUSPLUS) test/testmain.cc $(LIBS) -lboost_system>&/dev/null; then echo 1; else echo 0; fi),0)
@@ -328,9 +332,10 @@ GUI_LIBS=
328332
FLAGS+=-DBOOST_SIGNALS_NO_DEPRECATION_WARNING
329333

330334
# add the python module build here
335+
EXES+=$(PYMINSKY)
331336
ifeq ($(OS),Linux)
332337
ifndef MXE
333-
EXES+=pyminsky.so createLinkGroupIcons
338+
EXES+=createLinkGroupIcons
334339
endif
335340
endif
336341

@@ -445,8 +450,13 @@ endif
445450
libminsky.a: $(RESTSERVICE_OBJS) $(MODEL_OBJS) $(SCHEMA_OBJS) $(ENGINE_OBJS)
446451
ar r $@ $^
447452

448-
pyminsky.so: pyminsky.o libminsky.a
453+
$(PYMINSKY): pyminsky.o $(PYTHONCAPI) libminsky.a
454+
ifeq ($(OS),Darwin)
455+
c++ -bundle -undefined dynamic_lookup -Wl,-no_pie -Wl,-search_paths_first -mmacosx-version-min=$(MACOSX_MIN_VERSION) -arch $(ARCH) -stdlib=libc++ -o $@ $^ $(LIBS)
456+
cp pyminsky.so gui-js/build/
457+
else
449458
$(LINK) -shared -o $@ $^ libminsky.a $(LIBS)
459+
endif
450460

451461
# used to find undefined symbols in pyminsky.so
452462
pyminsky-test: test/testmain.o pyminsky.o libminsky.a
@@ -489,7 +499,8 @@ clean:
489499
mac-dist:
490500
# force rebuild of the node file to force rewriting of dependent dylibs
491501
rm -rf gui-js/build
492-
$(MAKE) gui-js/build/minskyRESTService.node
502+
$(MAKE) gui-js/build/minskyRESTService.node pyminsky.so
503+
cp pyminsky.so gui-js/build
493504
# create executable in the app package directory. Make it 32 bit only
494505
# mkdir -p minsky.app/Contents/MacOS
495506
# sh -v mkMacDist.sh

Readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Minsky is an open source program with prebuilt binaries available for:
1414
- [Mac OS X](https://sourceforge.net/projects/minsky/files/Mac%20Binaries/)
1515
- [various Linux distributions](https://build.opensuse.org/package/show/home:hpcoder1/minsky)
1616

17+
## Using Minsky in "batch mode" from Python
18+
A little known secret is that Minsky can be used in a headless batch mode, a capability it has had from the beginning in the form of TCL scripting. Since March 2024, Python scripting has been available in Linux builds, and is coming soon to Windows and Mac builds (version 3.12). TCL scripting is deprecated and will soon be removed from the codebase.
19+
20+
[Introduction to scripting Minsky in Python](python-minsky.md)
21+
1722
## Compiling Minsky from source code
1823

1924
Official releases are available from [SourceForge](https://sourceforge.net/projects/minsky/files/Sources/). You may also obtain later releases from this Github project, as release are tagged within git.

ecolab

Submodule ecolab updated 1 file

mkMacRESTService.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ rewrite_dylib()
4444
{
4545
local dylib=$1
4646
local target=$2
47-
cp -f $dylib $MAC_DIST_DIR
47+
if [ ! -x $MAC_DIST_DIR/${dylib##*/} ]; then
48+
cp -f $dylib $MAC_DIST_DIR
49+
fi
4850
chmod u+rw $MAC_DIST_DIR/${dylib##*/}
4951
rewrite_dylibs $MAC_DIST_DIR/${dylib##*/}
5052
echo "install_name_tool -change $dylib @loader_path/${dylib##*/} $target"
@@ -75,6 +77,7 @@ rm -rf $MAC_DIST_DIR/*.dylib
7577
mkdir -p $MAC_DIST_DIR
7678

7779
rewrite_dylibs $MAC_DIST_DIR/minskyRESTService.node
80+
rewrite_dylibs $MAC_DIST_DIR/pyminsky.so
7881

7982
pushd gui-js
8083
# libravel.so expects certain dylibs in node-addons for legacy

python-minsky.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Scripting Minsky in Python
2+
There are a number of applications for driving Minsky from scripts, ranging from automating tasks over multiple .mky files, regression tests, parameter sweeps and ensemble runs. Since early 2024. it has been possible to script Minsky using Python. Note Python 2 is _not_ supported, you must use Python 3.x
3+
4+
## Setting the python path to find the pyminsky module
5+
6+
- On *Linux*, this step is unnecessary if you have installed Minsky from the Open Build Service, as the pyminsky module is installed in a standard location for the standard python or python3 executable for that distro. If you are using a non-standard python version, you will need to copy or link the module (pyminsky.so) to somewhere in your python path.
7+
- On *Windows*, the python module (pyminsky.pyd) is installed in the same directory as Ravel executable. On my system that is $LOCALAPPDATA/Programs/minsky. So the following snippet should set up you python path:
8+
~~~~
9+
import sys,os
10+
sys.path.append(os.environ['LOCALAPPDATA']+'\\Programs\\minsky')
11+
~~~~
12+
- On *MacOS*, the python module (pyminsky.so) is located in the Resource directory of whereever you have installed ravel.app. Assuming you have installed this in the standard location, the following snippet should work
13+
~~~~
14+
import sys,os
15+
sys.path.append('/Applications/ravel.app/Contents/Resources/build')
16+
~~~~
17+
18+
## The minsky object
19+
Minsky is structured around a singleton object "minsky". So pyminsky has a minsky object that refers to the C++ Minsky singleton. An example usage is:
20+
~~~~
21+
from pyminsky import minsky
22+
print(minsky.minskyVersion())
23+
print(minsky.t()) # print the current timestep
24+
minsky.t(10.2) # set the timestep to 10.2
25+
~~~~
26+
27+
## C++ reflection
28+
The pyminsky module reflects the C++ code. Pretty much all public methods and attributes of the C++ classes are accessible from Python.
29+
30+
### Attributes
31+
Attributes appear as an overloaded _getter/setter_ pair. Calling the attribute without an argument is treated as a getter, and with an argument as a setter. C++ `bool`, numeric and string types map to the obvious Python equivalents. Standard vectors, lists and sets map to python lists. Standard maps map to a list first/second pairs containing the key-value pairs. Structs and classes are mapped to dicts.
32+
33+
Compound types have additional syntax elements. You can access the individual methods and attributes of a class or struct member, eg
34+
~~~~
35+
>>> minsky.model.items()
36+
[{'bb': {}, 'bookmark': False, 'deleteCallback': '', 'm_sf': 1.0, 'm_x': 0.0, 'm_y': 0.0, 'mouseFocus': False, 'onBorder': False, 'onResizeHandles': False, 'selected': False}]
37+
~~~~
38+
39+
Sequences can have a len operator applied, and be indexed
40+
~~~~
41+
>>> len(minsky.model.items())
42+
1
43+
>>> minsky.model.items[0]()
44+
{'bb': {}, 'bookmark': False, 'deleteCallback': '', 'm_sf': 1.0, 'm_x': 0.0, 'm_y': 0.0, 'mouseFocus': False, 'onBorder': False, 'onResizeHandles': False, 'selected': False}
45+
>>> minsky.model.items[0].name()
46+
'hello'
47+
~~~~
48+
49+
Maps can be indexed by their keys:
50+
~~~~
51+
>>> len(minsky.variableValues)
52+
3
53+
>>> minsky.variableValues.keys()
54+
[':hello', 'constant:one', 'constant:zero']
55+
>>> minsky.variableValues[':hello'].name()
56+
'hello'
57+
~~~~
58+
59+
### enums
60+
61+
enums are mapped to python strings. pyminsky has an enum dict containing all the enums defined in the system, and their possible values.
62+
~~~~
63+
>>> pyminsky.enum
64+
{'::minsky::Canvas::LassoMode::type': ['none', 'lasso', 'itemResize'], '::minsky::ClickType::Type': ['onItem', 'onPort', 'outside', 'inItem', 'onResize', 'legendMove', 'legendResize'], '::minsky::GodleyAssetClass::AssetClass': ['noAssetClass', 'asset', 'liability', 'equity'], '::minsky::GodleyAssetClass::DisplayStyle': ['DRCR', 'sign'], '::minsky::Group::IORegion::type': ['none', 'input', 'output', 'topBottom'], '::minsky::JustificationStruct::Justification': ['left', 'right', 'centre'], '::minsky::Minsky::CmdData': ['no_command', 'is_const', 'is_setterGetter', 'generic'], '::minsky::Minsky::MemCheckResult': ['OK', 'proceed', 'abort'], '::minsky::OperationType::Group': ['general', 'constop', 'binop', 'function', 'reduction', 'scan', 'tensor', 'statistics'], '::minsky::OperationType::Type': ['constant', 'time', 'integrate', 'differentiate', 'data', 'ravel', 'euler', 'pi', 'zero', 'one', 'inf', 'percent', 'add', 'subtract', 'multiply', 'divide', 'min', 'max', 'and_', 'or_', 'log', 'pow', 'polygamma', 'lt', 'le', 'eq', 'userFunction', 'copy', 'sqrt', 'exp', 'ln', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'abs', 'floor', 'frac', 'not_', 'Gamma', 'fact', 'sum', 'product', 'infimum', 'supremum', 'any', 'all', 'infIndex', 'supIndex', 'runningSum', 'runningProduct', 'difference', 'differencePlus', 'innerProduct', 'outerProduct', 'index', 'gather', 'meld', 'merge', 'slice', 'size', 'shape', 'mean', 'median', 'stdDev', 'moment', 'histogram', 'covariance', 'correlation', 'linearRegression', 'numOps'], '::minsky::VariableType::Type': ['undefined', 'constant', 'parameter', 'flow', 'integral', 'stock', 'tempFlow', 'numVarTypes']}
65+
~~~~
66+
67+
You can set/get their values like any other C++ type, but if you attempt to set an enum to a string value not in its range of values, nothing will happen, no error is raised.
68+
69+
### Method calls
70+
C++ methods are called, and passed python parameters are converted into their C++ equivalents where possible, and an exception thrown where not. Mostly, the obvious thing will work, but some peculiarities can occur. For example if a method is overloaded with both an int argument and a double argument, then calling the method on 3 will call the int version, but calling it on 3.0 will call the double version. Sometimes it can be non-obvious which method will be called, so some experimentation is in order. Structs, and class public members on parameters can be set by passing a dict object. members not set within the dict are set to the default values, as though default constructed in C++.
71+

0 commit comments

Comments
 (0)