FLAME  devel
 All Classes Functions Variables Typedefs Enumerations Pages
modmachine.cpp
1 #include <climits>
2 #include <sstream>
3 
4 #include "flame/base.h"
5 #include "pyflame.h"
6 
7 
8 #define TRY PyMachine *machine = reinterpret_cast<PyMachine*>(raw); try
9 
10 namespace {
11 
12 struct PyMachine {
13  PyObject_HEAD
14 
15  PyObject *weak;
16  Machine *machine;
17 };
18 
19 static
20 int PyMachine_init(PyObject *raw, PyObject *args, PyObject *kws)
21 {
22  TRY {
23  assert(!machine->weak);
24 
25  std::unique_ptr<Config> C(PyGLPSParse2Config(raw, args, kws));
26 
27  machine->machine = new Machine(*C);
28 
29  return 0;
30  } CATCH3(key_error, KeyError, -1)
31  CATCH3(std::invalid_argument, ValueError, -1)
32  CATCH3(std::exception, RuntimeError, -1)
33 }
34 
35 static
36 void PyMachine_free(PyObject *raw)
37 {
38  TRY {
39  std::unique_ptr<Machine> S(machine->machine);
40  machine->machine = NULL;
41 
42  if(machine->weak)
43  PyObject_ClearWeakRefs(raw);
44 
45  Py_TYPE(raw)->tp_free(raw);
46  } CATCH2V(std::exception, RuntimeError)
47 }
48 
49 static
50 PyObject *PyMachine_str(PyObject *raw)
51 {
52  TRY {
53  std::ostringstream strm;
54  strm << *(machine->machine);
55  return PyString_FromString(strm.str().c_str());
56  } CATCH()
57 }
58 
59 static
60 PyObject *PyMachine_conf(PyObject *raw, PyObject *args, PyObject *kws)
61 {
62  TRY {
63  PyObject *pyindex = Py_None;
64  const char *pnames[] = {"index", NULL};
65  if(!PyArg_ParseTupleAndKeywords(args, kws, "|O", (char**)pnames, &pyindex))
66  return NULL;
67 
68  Config C;
69  if(pyindex==Py_None) {
70  C = machine->machine->conf();
71  } else if(PyNumber_Check(pyindex)) {
72  PyRef<> pylong(PyNumber_Long(pyindex));
73  long index = PyLong_AsLong(pylong.py());
74  if(index<0 || (unsigned long)index>=machine->machine->size())
75  return PyErr_Format(PyExc_IndexError, "Element index out of range");
76  C = (*machine->machine)[index]->conf();
77  } else {
78  return PyErr_Format(PyExc_ValueError, "'index' must be an integer or None");
79  }
80  C.flatten();
81 
82  return conf2dict(&C);
83  } CATCH()
84 }
85 
86 static
87 PyObject *PyMachine_allocState(PyObject *raw, PyObject *args, PyObject *kws)
88 {
89  TRY {
90  PyObject *d = Py_None, *W = Py_False;
91  const char *pnames[] = {"config", "inherit", NULL};
92  if(!PyArg_ParseTupleAndKeywords(args, kws, "|OO", (char**)pnames, &d, &W))
93  return NULL;
94 
95  Config C;
96  if(d==Py_None) {
97  C = machine->machine->conf();
98  } else if(PyDict_Check(d)) {
99  if(PyObject_IsTrue(W)) {
100  C = machine->machine->conf();
101  C.push_scope();
102  }
103  PyRef<> list(PyMapping_Items(d));
104  List2Config(C, list.py());
105  } else {
106  return PyErr_Format(PyExc_ValueError, "allocState() needs config=None or {}");
107  }
108  std::unique_ptr<StateBase> state(machine->machine->allocState(C));
109  PyObject *ret = wrapstate(state.get());
110  state.release();
111  return ret;
112  } CATCH()
113 }
114 
115 struct PyStoreObserver : public Observer
116 {
117  PyRef<> list;
118  PyStoreObserver()
119  :list(PyList_New(0))
120  {}
121  virtual ~PyStoreObserver() {}
122  virtual void view(const ElementVoid* elem, const StateBase* state)
123  {
124  PyRef<> tuple(PyTuple_New(2));
125  std::unique_ptr<StateBase> tmpstate(state->clone());
126  PyRef<> statecopy(wrapstate(tmpstate.get()));
127  tmpstate.release();
128 
129  PyTuple_SET_ITEM(tuple.py(), 0, PyInt_FromSize_t(elem->index));
130  PyTuple_SET_ITEM(tuple.py(), 1, statecopy.release());
131  if(PyList_Append(list.py(), tuple.py()))
132  throw std::runtime_error(""); // a py exception is active
133  }
134 };
135 
136 struct PyScopedObserver
137 {
138  Machine *machine;
139  std::vector<size_t> observed;
140  PyScopedObserver(Machine *m) : machine(m) {}
141  ~PyScopedObserver() {
142  for(size_t i=0; i<machine->size(); i++) {
143  (*machine)[i]->set_observer(NULL);
144  }
145  }
146  void observe(size_t i, Observer *o)
147  {
148  if(i>=machine->size())
149  throw std::runtime_error("element index out of range");
150  observed.push_back(i);
151  (*machine)[i]->set_observer(o);
152  }
153 };
154 
155 static
156 PyObject *PyMachine_propagate(PyObject *raw, PyObject *args, PyObject *kws)
157 {
158 
159  TRY {
160  PyObject *state, *toobserv = Py_None, *pymax = Py_None;
161  unsigned long start = 0;
162  int max = INT_MAX;
163  const char *pnames[] = {"state", "start", "max", "observe", NULL};
164  if(!PyArg_ParseTupleAndKeywords(args, kws, "O|kOO", (char**)pnames, &state, &start, &pymax, &toobserv))
165  return NULL;
166 
167  if (pymax!=Py_None) max = (int) PyLong_AsLong(pymax);
168 
169  PyStoreObserver observer;
170  PyScopedObserver observing(machine->machine);
171 
172  if(toobserv!=Py_None) {
173  PyRef<> iter(PyObject_GetIter(toobserv)), item;
174 
175  while(item.reset(PyIter_Next(iter.py()), PyRef<>::allow_null())) {
176  Py_ssize_t num = PyNumber_AsSsize_t(item.py(), PyExc_ValueError);
177  if(PyErr_Occurred())
178  throw std::runtime_error(""); // caller will get active python exception
179  observing.observe(num, &observer);
180 
181  }
182  }
183 
184  machine->machine->propagate(unwrapstate(state), start, max);
185  if(toobserv) {
186  return observer.list.release();
187  } else {
188  Py_RETURN_NONE;
189  }
190  } CATCH2(std::invalid_argument, ValueError)
191  CATCH()
192 }
193 
194 static
195 PyObject *PyMachine_reconfigure(PyObject *raw, PyObject *args, PyObject *kws)
196 {
197  TRY{
198  unsigned long idx;
199  PyObject *conf, *replace = Py_False;
200  const char *pnames[] = {"index", "config", "replace", NULL};
201  if(!PyArg_ParseTupleAndKeywords(args, kws, "kO!|O", (char**)pnames, &idx, &PyDict_Type, &conf, &replace))
202  return NULL;
203 
204  if(idx>=machine->machine->size())
205  return PyErr_Format(PyExc_ValueError, "invalid element index %lu", idx);
206 
207  Config newconf;
208  if(!PyObject_IsTrue(replace))
209  newconf = (*machine->machine)[idx]->conf();
210 
211  PyRef<> list(PyMapping_Items(conf));
212  List2Config(newconf, list.py(), 3); // set depth=3 to prevent recursion
213 
214  machine->machine->reconfigure(idx, newconf);
215 
216  Py_RETURN_NONE;
217  } CATCH2(std::invalid_argument, ValueError)
218  CATCH()
219 }
220 
221 static
222 PyObject *PyMachine_find(PyObject *raw, PyObject *args, PyObject *kws)
223 {
224  TRY{
225  const char *ename = NULL, *etype = NULL;
226  const char *pnames[] = {"name", "type", NULL};
227  if(!PyArg_ParseTupleAndKeywords(args, kws, "|zz", (char**)pnames, &ename, &etype))
228  return NULL;
229 
230  PyRef<> ret(PyList_New(0));
231 
232  std::pair<Machine::lookup_iterator, Machine::lookup_iterator> range;
233 
234  if(ename && etype) {
235  return PyErr_Format(PyExc_ValueError, "only one of 'name' or 'type' may be given");
236  } else if(ename) {
237  range = machine->machine->equal_range(ename);
238  } else if(etype) {
239  range = machine->machine->equal_range_type(etype);
240  } else {
241  range = machine->machine->all_range();
242  }
243 
244  for(; range.first!=range.second; ++range.first) {
245  ElementVoid *elem = *range.first;
246 
247  PyRef<> pyidx(PyInt_FromLong(elem->index));
248 
249  if(PyList_Append(ret.py(), pyidx.py()))
250  return NULL;
251  }
252 
253  return ret.release();
254  }CATCH()
255 }
256 
257 static
258 Py_ssize_t PyMachine_len(PyObject *raw)
259 {
260  TRY{
261  return machine->machine->size();
262  }CATCH1(-1)
263 }
264 
265 static PyMethodDef PyMachine_methods[] = {
266  {"conf", (PyCFunction)&PyMachine_conf, METH_VARARGS|METH_KEYWORDS,
267  "conf() -> {} Machine config\n"
268  "conf(index) -> {} Element config"},
269  {"allocState", (PyCFunction)&PyMachine_allocState, METH_VARARGS|METH_KEYWORDS,
270  "allocState() -> State\n"
271  "allocState({'variable':int|str}) -> State\n"
272  "Allocate a new State based on this Machine's configuration."
273  " Optionally provide additional configuration"},
274  {"propagate", (PyCFunction)&PyMachine_propagate, METH_VARARGS|METH_KEYWORDS,
275  "propagate(State, start=0, max=INT_MAX, observe=None)\n"
276  "propagate(State, start=0, max=INT_MAX, observe=[1,4,...]) -> [(index,State), ...]\n"
277  "Propagate the provided State through the simulation.\n"
278  "\n"
279  "start and max selects through which element the State will be passed.\n"
280  "\n"
281  "observe may be None or an iterable yielding element indicies.\n"
282  "In the second form propagate() returns a list of tuples with the output State of the selected elements."
283  },
284  {"reconfigure", (PyCFunction)&PyMachine_reconfigure, METH_VARARGS|METH_KEYWORDS,
285  "reconfigure(index, {'variable':int|str})\n"
286  "Change the configuration of an element."},
287  {"find", (PyCFunction)&PyMachine_find, METH_VARARGS|METH_KEYWORDS,
288  "find(name=None, type=None) -> [int]\n"
289  "Return a list of element indices for element name or type matching the given string."},
290  {NULL, NULL, 0, NULL}
291 };
292 
293 static PySequenceMethods PyMachine_seq = {
294  &PyMachine_len
295 };
296 
297 static PyTypeObject PyMachineType = {
298 #if PY_MAJOR_VERSION >= 3
299  PyVarObject_HEAD_INIT(NULL, 0)
300 #else
301  PyObject_HEAD_INIT(NULL)
302  0,
303 #endif
304  "flame._internal.Machine",
305  sizeof(PyMachine),
306 };
307 
308 } // namespace
309 
310 static const char pymdoc[] =
311  "Machine(config, path=None, extra=None)\n"
312  "Machine(config, path='/directry/', extra={'variable':float|str}})\n"
313  "\n"
314  "A Machine() the primary interface to the FLAME simulation engine.\n"
315  "\n"
316  "The 'config' argument may be a file-like object (with read())"
317  " or a buffer which will be parsed with the GLPS parser (see GLPSParser::parse).\n"
318  " Or it may be a dictionary.\n"
319  "\n"
320  ">>> with open('some.lat', 'rb') as F:\n"
321  " M = Machine(F)\n"
322  ">>>\n"
323  ;
324 
325 int registerModMachine(PyObject *mod)
326 {
327  PyMachineType.tp_doc = pymdoc;
328  PyMachineType.tp_str = &PyMachine_str;
329 
330  PyMachineType.tp_new = &PyType_GenericNew;
331  PyMachineType.tp_init = &PyMachine_init;
332  PyMachineType.tp_dealloc = &PyMachine_free;
333 
334  PyMachineType.tp_weaklistoffset = offsetof(PyMachine, weak);
335 
336  PyMachineType.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE;
337  PyMachineType.tp_methods = PyMachine_methods;
338  PyMachineType.tp_as_sequence = &PyMachine_seq;
339 
340  if(PyType_Ready(&PyMachineType))
341  return -1;
342 
343  Py_INCREF((PyObject*)&PyMachineType);
344  if(PyModule_AddObject(mod, "Machine", (PyObject*)&PyMachineType)) {
345  Py_DECREF((PyObject*)&PyMachineType);
346  return -1;
347  }
348 
349  return 0;
350 }
Base class for all simulated elements.
Definition: base.h:165
virtual void view(const ElementVoid *elem, const StateBase *state)=0
Called from within Machine::propagate()
The core simulate Machine engine.
Definition: base.h:229
virtual StateBase * clone() const =0
The abstract base class for all simulation state objects.
Definition: base.h:29
Associative configuration container.
Definition: config.h:66
size_t index
Index of this element (unique in its Machine)
Definition: base.h:193
Allow inspection of intermediate State.
Definition: base.h:152