FLAME  devel
 All Classes Functions Variables Typedefs Enumerations Pages
modmachine.cpp
1 
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::auto_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::auto_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::auto_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::auto_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<observed.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  if((*machine)[i]->observer())
151  throw std::runtime_error("element already observed");
152  observed.push_back(i);
153  (*machine)[i]->set_observer(o);
154  }
155 };
156 
157 static
158 PyObject *PyMachine_propagate(PyObject *raw, PyObject *args, PyObject *kws)
159 {
160 
161  TRY {
162  PyObject *state, *toobserv = Py_None;
163  unsigned long start = 0, max = (unsigned long)-1;
164  const char *pnames[] = {"state", "start", "max", "observe", NULL};
165  if(!PyArg_ParseTupleAndKeywords(args, kws, "O|kkO", (char**)pnames, &state, &start, &max, &toobserv))
166  return NULL;
167 
168  PyStoreObserver observer;
169  PyScopedObserver observing(machine->machine);
170 
171  if(toobserv!=Py_None) {
172  PyRef<> iter(PyObject_GetIter(toobserv)), item;
173 
174  while(item.reset(PyIter_Next(iter.py()), PyRef<>::allow_null())) {
175  Py_ssize_t num = PyNumber_AsSsize_t(item.py(), PyExc_ValueError);
176  if(PyErr_Occurred())
177  throw std::runtime_error(""); // caller will get active python exception
178 
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 'ename' or 'etype' 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=-1, observe=None)\n"
276  "propagate(State, start=0, max=-1, 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(ename=None, etype=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 }
void propagate(StateBase *S, size_t start=0, size_t max=-1) const
Pass the given bunch State through this Machine.
Definition: base.cpp:167
Base class for all simulated elements.
Definition: base.h:166
virtual void view(const ElementVoid *elem, const StateBase *state)=0
Called from within Machine::propagate()
The core simulate Machine engine.
Definition: base.h:230
STL namespace.
virtual StateBase * clone() const =0
size_t size() const
Definition: base.h:319
The abstract base class for all simulation state objects.
Definition: base.h:28
std::pair< lookup_iterator, lookup_iterator > equal_range_type(const std::string &name)
Definition: base.h:379
Associative configuration container.
Definition: config.h:66
Allow inspection of intermediate State.
Definition: base.h:153
void reconfigure(size_t idx, const Config &c)
Change the configuration of a single element.
Definition: base.cpp:191
const size_t index
Index of this element (unique in its Machine)
Definition: base.h:194
std::pair< lookup_iterator, lookup_iterator > equal_range(const std::string &name)
Definition: base.h:370