source: cpp/frams/neuro/neuroimpl.cpp @ 874

Last change on this file since 874 was 732, checked in by Maciej Komosinski, 7 years ago

Added support for "checkpoints" (intermediate phases of development of the Model when converting between genetic encodings). See Model.checkpoint() and conv_f1.cpp for an example.

  • Property svn:eol-style set to native
File size: 17.8 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2018  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include "neuroimpl.h"
6#include "neurofactory.h"
7#include <frams/util/rndutil.h>
8#include <common/nonstd_math.h>
9#ifndef SDK_WITHOUT_FRAMS
10#include <frams/simul/creature.h>
11#include <frams/mech/creatmechobj.h>
12#include <frams/simul/livegroups.h>
13#include <frams/simul/simul.h>
14#endif
15
16const int NeuroImpl::ENDDRAWING = -9999;
17const int NeuroImpl::MAXDRAWINGXY = 0xffff;
18
19int NeuroNetImpl::mytags_id = 0;
20
21/////////////////////////////////////////////////////////
22
23#define FIELDSTRUCT NeuroNetConfig
24static ParamEntry nncfg_paramtab[] =
25{
26        { "Creature: Neurons", 1, 3, "nnsim", },
27        { "randinit", 1, 0, "Random initialization", "f 0 10 0.01", FIELD(randominit), "Allowed range for initializing all neuron states with uniform distribution random numbers and zero mean. Set to 0 for deterministic initialization." },
28        { "nnoise", 1, 0, "Noise", "f 0 1 0", FIELD(nnoise), "Gaussian neural noise: a random value is added to each neural output in each simulation step. Set standard deviation here to add random noise, or 0 for deterministic simulation." },
29        { "touchrange", 1, 0, "T receptor range", "f 0 100 1", FIELD(touchrange), },
30        { 0, 0, 0, },
31};
32#undef FIELDSTRUCT
33
34NeuroNetConfig::NeuroNetConfig(NeuroFactory *fac)
35        :par(nncfg_paramtab, this),
36        randominit(0.01),
37        nnoise(0),
38        touchrange(1),
39        factory(fac)
40{}
41
42/////////////////////////////////////////////////////////////////
43
44NeuroNetImpl::NeuroNetImpl(Model& model, NeuroNetConfig& conf
45#ifdef NEURO_SIGNALS
46        , ChannelSpace *ch
47#endif
48        )
49        :mod(model), config(conf),
50        isbuilt(1), errorcount(0)
51#ifdef NEURO_SIGNALS
52        , channels(ch)
53#endif
54{
55        if (!mytags_id) mytags_id = mod.userdata.newID();
56
57        Neuro *n;
58        NeuroImpl *ni;
59        Joint *j;
60        int i;
61        DB(printf("makeNeuroNet(%p)\n", &mod));
62
63        minorder = 3; maxorder = 0;
64        errorcount = 0;
65
66        for (i = 0; j = mod.getJoint(i); i++)
67                j->flags &= ~(4 + 8); // todo: !!!neuroitems shouldn't use model fields!!!
68
69        for (i = 0; n = mod.getNeuro(i); i++)
70        {
71                ni = conf.factory->createNeuroImpl(n);
72                n->userdata[mytags_id] = ni;
73                if (!ni)
74                {
75                        errorcount++;
76                        logPrintf("NeuroNetImpl", "create", LOG_WARN, "neuron #%d (%s) implementation not available",
77                                i, n->getClassName().c_str());
78                        continue;
79                } // implementation not available?!
80                ni->owner = this;
81                ni->neuro = n;
82                ni->readParam();
83        }
84
85        for (i = 0; n = mod.getNeuro(i); i++)
86        {
87                n->state += (rnd01 - 0.5)*config.randominit;
88                ni = (NeuroImpl*)n->userdata[mytags_id];
89                if (!ni) continue;
90                if (!ni->lateinit())
91                {
92                        ni->status = NeuroImpl::InitError;
93                        errorcount++;
94                        logPrintf("NeuroNetImpl", "create", LOG_WARN, "neuron #%d (%s) initialization failed",
95                                i, n->getClassName().c_str());
96                        continue;
97                }
98                ni->status = NeuroImpl::InitOk;
99                int order = ni->getSimOrder();
100                if (order < 0) order = 0; else if (order>2) order = 2;
101                if (order < minorder) minorder = order;
102                if (order > maxorder) maxorder = order;
103                neurons[order] += ni;
104                if (ni->getNeedPhysics())
105                        neurons[3] += ni;
106        }
107        cnode = mod.delmodel_list.add(STATRICKCALLBACK(this, &NeuroNetImpl::destroyNN, 0));
108}
109
110void NeuroNetImpl::destroyNN(CALLBACKARGS)
111{
112        if (!isbuilt) return;
113        DB(printf("destroyNeuroNet(%p)\n", &mod));
114        NeuroImpl *ni;
115        Neuro *n;
116        for (int i = 0; n = mod.getNeuro(i); i++)
117        {
118                ni = (NeuroImpl*)n->userdata[mytags_id];
119                delete ni;
120                n->userdata[mytags_id] = 0;
121        }
122        mod.delmodel_list.remove(cnode);
123        isbuilt = 0; errorcount = 0;
124        delete this;
125}
126
127NeuroNetImpl::~NeuroNetImpl()
128{
129        destroyNN(0, 0);
130}
131
132void NeuroNetImpl::simulateNeuroNet()
133{
134        NeuroImpl *ni;
135        for (int order = minorder; order <= maxorder; order++)
136        {
137                int i;
138                SList &nlist = neurons[order];
139                for (i = 0; ni = (NeuroImpl*)nlist(i); i++)
140                        ni->go();
141                for (i = 0; ni = (NeuroImpl*)nlist(i); i++)
142                        ni->commit();
143        }
144}
145
146void NeuroNetImpl::simulateNeuroPhysics()
147{
148        NeuroImpl *ni;
149        int i;
150        SList &nlist = neurons[3];
151        for (i = 0; ni = (NeuroImpl*)nlist(i); i++)
152                ni->goPhysics();
153}
154
155///////////////////////////////////////////////
156
157void NeuroImpl::setChannelCount(int c)
158{
159        if (c < 1) c = 1;
160        if (c == channels) return;
161        if (c < channels) { channels = c; chstate.trim(c - 1); chnewstate.trim(c - 1); return; }
162        double s = getState(channels - 1);
163        chnewstate.setSize(c - 1);
164        chstate.setSize(c - 1);
165        for (int i = channels; i < c; i++)
166        {
167                chstate(i - 1) = s;
168                chnewstate(i - 1) = s;
169        }
170        channels = c;
171}
172
173void NeuroImpl::setState(double st, int channel)
174{
175        validateNeuroState(st);
176        if (channel >= channels) channel = channels - 1;
177        if (channel <= 0) { newstate = st; return; }
178        chnewstate(channel - 1) = st;
179}
180
181void NeuroImpl::setCurrentState(double st, int channel)
182{
183        validateNeuroState(st);
184        if (channel >= channels) channel = channels - 1;
185        if (channel <= 0) { neuro->state = st; return; }
186        chstate(channel - 1) = st;
187}
188
189double NeuroImpl::getNewState(int channel)
190{
191        if (neuro->flags&Neuro::HoldState) return getState(channel);
192        if (channel >= channels) channel = channels - 1;
193        if (channel <= 0) { return newstate; }
194        return chnewstate(channel - 1);
195}
196
197double NeuroImpl::getState(int channel)
198{
199        if (channel >= channels) channel = channels - 1;
200        if (channel <= 0) return neuro->state;
201        return chstate(channel - 1);
202}
203
204void NeuroImpl::commit()
205{
206        if (!(neuro->flags&Neuro::HoldState))
207        {
208                if (channels > 1)
209                        chstate = chnewstate;
210                neuro->state = newstate;
211                if (owner->getConfig().nnoise > 0.0)
212                {
213                        neuro->state += RndGen.GaussStd()*owner->getConfig().nnoise;
214                        if (channels > 1)
215                                for (int i = 0; i < chstate.size(); i++)
216                                        chstate(0) += RndGen.GaussStd()*owner->getConfig().nnoise;
217                }
218        }
219}
220
221int NeuroImpl::getInputChannelCount(int i)
222{
223        if ((i < 0) || (i >= neuro->getInputCount())) return 1;
224        Neuro *nu = neuro->getInput(i);
225        NeuroImpl *ni = NeuroNetImpl::getImpl(nu);
226        if (!ni) return 1;
227        return ni->channels;
228}
229
230double NeuroImpl::getInputState(int i, int channel)
231{
232        if ((i < 0) || (i >= neuro->getInputCount())) return 0;
233        Neuro *nu = neuro->getInput(i);
234        if (channel <= 0) return nu->state;
235        NeuroImpl *ni = NeuroNetImpl::getImpl(nu);
236        if (!ni) return nu->state;
237        if (channel >= ni->channels) channel = ni->channels - 1;
238        if (!channel) return nu->state;
239        return ni->chstate(channel - 1);
240}
241
242double NeuroImpl::getWeightedInputState(int i, int channel)
243{
244        if ((i < 0) || (i >= neuro->getInputCount())) return 0;
245        double w;
246        Neuro *nu = neuro->getInput(i, w);
247        if (channel <= 0) return nu->state * w;
248        NeuroImpl *ni = NeuroNetImpl::getImpl(nu);
249        if (!ni) return nu->state * w;
250        if (channel >= ni->channels) channel = ni->channels - 1;
251        if (!channel) return w * nu->state;
252        return w * ni->chstate(channel - 1);
253}
254
255double NeuroImpl::getInputSum(int startwith)
256{
257        if (startwith < 0) return 0;
258        Neuro *inp;
259        double sum = 0.0;
260        while (inp = neuro->getInput(startwith++))
261                sum += inp->state;
262        return sum;
263}
264
265double NeuroImpl::getWeightedInputSum(int startwith)
266{
267        if (startwith < 0) return 0;
268        Neuro *inp;
269        double sum = 0.0;
270        double w;
271        while (inp = neuro->getInput(startwith++, w))
272                sum += inp->state*w;
273        return sum;
274}
275
276void NeuroImpl::readParam()
277{
278        Param par;
279        if (!paramentries) return;
280        par.setParamTab(paramentries);
281        par.select(this);
282        par.setDefault();
283        par.load(ParamInterface::FormatSingleLine, neuro->getClassParams());
284}
285
286Param& NeuroImpl::getStaticParam()
287{
288        static Param p(neuroimpl_tab, 0, "Neuro");
289        return p;
290}
291
292/////////////////////////////
293
294#ifdef NEURO_SIGNALS
295#define NEUROIMPL_SIGNAL_PROPS 1
296#else
297#define NEUROIMPL_SIGNAL_PROPS 0
298#endif
299
300#define FIELDSTRUCT NeuroImpl
301ParamEntry neuroimpl_tab[] =
302{
303        { "Neuro", 1, 27 + NEUROIMPL_SIGNAL_PROPS, "Neuro", "Live Neuron object." },
304
305        { "getInputState", 0, 0, "Get input signal", "p f(d input)", PROCEDURE(p_get), },
306        { "getInputWeight", 0, 0, "Get input weight", "p f(d input)", PROCEDURE(p_getweight), },
307        { "getWeightedInputState", 0, 0, "Get weighted input signal", "p f(d input)", PROCEDURE(p_getw), },
308        { "getInputSum", 0, 0, "Get signal sum", "p f(d input)", PROCEDURE(p_getsum), },
309        { "getWeightedInputSum", 0, 0, "Get weighted signal sum", "p f(d input)", PROCEDURE(p_getwsum), "Uses any number of inputs starting with the specified input. getWeightedInputSum(0)=weightedInputSum" },
310        { "getInputCount", 0, PARAM_READONLY, "Get input count", "d", GETONLY(count), },
311        { "inputSum", 0, PARAM_READONLY, "Full signal sum", "f", GETONLY(sum), },
312        { "weightedInputSum", 0, PARAM_READONLY, "Full weighted signal sum", "f", GETONLY(wsum), },
313        { "getInputChannelCount", 0, 0, "Get channel count for input", "p d(d input)", PROCEDURE(p_getchancount), },
314        { "getInputStateChannel", 0, 0, "Get input signal from channel", "p f(d input,d channel)", PROCEDURE(p_getchan), },
315        { "getWeightedInputStateChannel", 0, 0, "Get weighted input signal from channel", "p f(d input,d channel)", PROCEDURE(p_getwchan), },
316        { "state", 0, 0, "Neuron state (channel 0)", "f", GETSET(state), "When read, returns the current neuron state.\nWhen written, sets the 'internal' neuron state that will become current in the next step.\nTypically you should use this field, and not currState." },
317        { "channelCount", 0, 0, "Number of output channels", "d", GETSET(channels), },
318        { "getStateChannel", 0, 0, "Get state for channel", "p f(d channel)", PROCEDURE(p_getstate), },
319        { "setStateChannel", 0, 0, "Set state for channel", "p(d channel,f value)", PROCEDURE(p_setstate), },
320        { "hold", 0, 0, "Hold state", "d 0 1", GETSET(hold), "\"Holding\" means keeping the neuron state as is, blocking the regular neuron operation. This is useful when your script needs to inject some control signals into the NN. Without \"holding\", live neurons would be constantly overwriting your changes, and the rest of the NN could see inconsistent states, depending on the connections. Setting hold=1 ensures the neuron state will be only set by you, and not by the neuron. The enforced signal value can be set using Neuro.currState before or after setting hold=1. Set hold=0 to resume normal operation.", },
321        { "currState", 0, 0, "Current neuron state (channel 0)", "f", GETSET(cstate), "When read, it behaves just like the 'state' field.\nWhen written, changes the current neuron state immediately, which disturbs the regular synchronous NN operation.\nThis feature should only be used while controlling the neuron 'from outside' (like a neuro probe) and not in the neuron definition. See also: Neuro.hold", },
322        { "setCurrStateChannel", 0, 0, "Set current neuron state for channel", "p(d channel,f value)", PROCEDURE(p_setcstate), "Analogous to \"currState\"." },
323        { "position_x", 0, PARAM_READONLY, "Position x", "f", GETONLY(position_x), },
324        { "position_y", 0, PARAM_READONLY, "Position y", "f", GETONLY(position_y), },
325        { "position_z", 0, PARAM_READONLY, "Position z", "f", GETONLY(position_z), },
326        { "creature", 0, PARAM_READONLY, "Gets owner creature", "oCreature", GETONLY(creature), },
327        { "part", 0, PARAM_READONLY, "The Part object where this neuron is located", "oMechPart", GETONLY(part), },
328        { "joint", 0, PARAM_READONLY, "The Joint object where this neuron is located", "oMechJoint", GETONLY(joint), },
329        { "neuroproperties", 0, PARAM_READONLY, "Custom neuron fields", "oNeuroProperties", GETONLY(fields),
330        "Neurons can have different fields depending on their class. Script neurons have their fields defined using the \"property:\" syntax. If you develop a custom neuron script you should use the NeuroProperties object for accessing your own neuron fields. The Neuro.neuroproperties property is meant for accessing the neuron fields from the outside script.\n"
331        "Examples:\n"
332        "var c=Populations.createFromString(\"X[N]\");\n"
333        "Simulator.print(\"standard neuron inertia=\"+c.getNeuro(0).neuroproperties.in);\n"
334        "c=Populations.createFromString(\"X[Nn,e:0.1]\");\n"
335        "Simulator.print(\"noisy neuron error rate=\"+c.getNeuro(0).neuroproperties.e);\n"
336        "\n"
337        "The Interface object can be used to discover which fields are available for a certain neuron object:\n"
338        "c=Populations.createFromString(\"X[N]\");\n"
339        "var iobj=Interface.makeFrom(c.getNeuro(0).neuroproperties);\n"
340        "var i;\n"
341        "for(i=0;i<iobj.size;i++)\n"
342        " Simulator.print(iobj.getId(i)+\" (\"+iobj.getName(i)+\")\");", },
343        { "def", 0, PARAM_READONLY, "Neuron definition from which this live neuron was built", "oNeuroDef", GETONLY(neurodef), },
344        { "classObject", 0, PARAM_READONLY, "Neuron class for this neuron", "oNeuroClass", GETONLY(classObject), },
345#ifdef NEURO_SIGNALS
346        { "signals", 0, PARAM_READONLY, "Signals", "oNeuroSignals", FIELD(sigs_obj), },
347#endif
348
349        { 0, 0, 0, },
350};
351#undef FIELDSTRUCT
352
353#ifdef NEURO_SIGNALS
354ParamEntry neurosignals_paramtab[] =
355{
356        { "NeuroSignals", 1, 8, "NeuroSignals", "Signals attached to a neuron.\nSee also: Signal, WorldSignals, CreatureSignals.\nscripts/light.neuro and scripts/seelight.neuro are simple custom neuron examples demonstrating how to send/receive signals between creatures.", },
357
358#define FIELDSTRUCT NeuroSignals
359        SIGNPAR_ADD(""),
360        SIGNPAR_RECEIVE(""),
361        SIGNPAR_RECEIVESET(""),
362        SIGNPAR_RECEIVEFILTER(""),
363        SIGNPAR_RECEIVESINGLE(""),
364#undef FIELDSTRUCT
365
366#define FIELDSTRUCT SignalSet
367        SIGNSETPAR_GET,
368        SIGNSETPAR_SIZE,
369        SIGNSETPAR_CLEAR,
370#undef FIELDSTRUCT
371        { 0, 0, 0, },
372};
373
374Param& NeuroSignals::getStaticParam()
375{
376        static Param p(neurosignals_paramtab, 0);
377        return p;
378}
379#endif
380
381#ifdef NEURO_SIGNALS
382class NeuroSigSource : public SigSource
383{
384protected:
385        NeuroImpl* owner;
386public:
387        NeuroSigSource(NeuroImpl *n, Creature *c) :SigSource(0, c), owner(n) {}
388        bool update();
389};
390
391bool NeuroSigSource::update()
392{
393        Pt3D p;
394        if (owner->getPosition(p))
395        {
396                setLocation(p);
397                return true;
398        }
399        return false;
400}
401
402Creature *NeuroSignals::getCreature()
403{
404        if (!cr)
405        {
406                cr = owner->getCreature();
407        }
408        return cr;
409}
410
411void NeuroSignals::p_add(PARAMPROCARGS)
412{
413        SigSource *s = new NeuroSigSource(owner, getCreature());
414        if (owner->owner->channels)
415        {
416                SigChannel *ch = owner->owner->channels->getChannel(args->getString(), true);
417                ch->addSource(s);
418        }
419        else
420                SigChannel::dummy_channel.addSource(s);
421        sigs += s;
422        s->setupObject(ret);
423}
424
425void NeuroSignals::p_receive(PARAMPROCARGS)
426{
427        SigChannel *ch; Pt3D p;
428        if (owner->owner->channels && (ch = owner->owner->channels->getChannel(args->getString(), false)) && owner->getPosition(p))
429                ret->setDouble(ch->receive(&p, getCreature()));
430        else
431                ret->setDouble(0);
432}
433
434void NeuroSignals::p_receiveFilter(PARAMPROCARGS)
435{
436        SigChannel *ch; Pt3D p;
437        if (owner->owner->channels && (ch = owner->owner->channels->getChannel(args[3].getString(), false)) && owner->getPosition(p))
438                ret->setDouble(ch->receive(&p, getCreature(), args[2].getDouble(), args[1].getDouble(), args[0].getDouble()));
439        else
440                ret->setDouble(0);
441}
442
443void NeuroSignals::p_receiveSet(PARAMPROCARGS)
444{
445        SigChannel *ch; Pt3D p;
446        SigVector *vec = new SigVector();
447        if (owner->owner->channels && (ch = owner->owner->channels->getChannel(args[1].getString(), false)) && owner->getPosition(p))
448                ch->receiveSet(vec, &p, getCreature(), args[0].getDouble());
449        ret->setObject(vec->makeObject());
450}
451
452void NeuroSignals::p_receiveSingle(PARAMPROCARGS)
453{
454        SigChannel *ch; Pt3D p;
455        if (owner->owner->channels && (ch = owner->owner->channels->getChannel(args[1].getString(), false)) && owner->getPosition(p))
456        {
457                SigSource *src = ch->receiveSingle(&p, getCreature(), args[0].getDouble(), 0, 1e99);
458                if (src)
459                {
460                        src->setupObject(ret);
461                        return;
462                }
463        }
464        ret->setEmpty();
465}
466#endif
467
468#ifndef SDK_WITHOUT_FRAMS
469extern ParamEntry creature_paramtab[];
470static Param creature_param(creature_paramtab, 0);
471#endif
472
473Creature* NeuroImpl::getCreature()
474{
475#ifndef SDK_WITHOUT_FRAMS
476        CreatMechObject *cmo = (CreatMechObject *)neuro->owner->userdata[CreatMechObject::modeltags_id];
477        return cmo->creature;
478#else
479        return 0;
480#endif
481}
482
483void NeuroImpl::get_creature(ExtValue *ret)
484{
485#ifndef SDK_WITHOUT_FRAMS
486        ret->setObject(ExtObject(&creature_param, getCreature()));
487#endif
488}
489
490void NeuroImpl::get_part(ExtValue *ret)
491{
492#ifndef SDK_WITHOUT_FRAMS
493        Part *pa;
494        if (pa = neuro->getPart())
495                ret->setObject(ExtObject(&MechPart::getStaticParam(), ((MechPart *)pa->userdata[CreatMechObject::modeltags_id])));
496        else
497                ret->setEmpty();
498#endif
499}
500
501void NeuroImpl::get_joint(ExtValue *ret)
502{
503#ifndef SDK_WITHOUT_FRAMS
504        Joint *jo;
505        if (jo = neuro->getJoint())
506                ret->setObject(ExtObject(&MechJoint::getStaticParam(), ((MechJoint*)jo->userdata[CreatMechObject::modeltags_id])));
507        else
508                ret->setEmpty();
509#endif
510}
511
512bool NeuroImpl::getPosition(Pt3D &pos)
513{
514#ifndef SDK_WITHOUT_FRAMS
515        Part *pa; Joint *jo;
516        if (pa = neuro->getPart())
517        {
518                pos = ((MechPart *)pa->userdata[CreatMechObject::modeltags_id])->p;
519                return true;
520        }
521        if (jo = neuro->getJoint())
522        {
523                if (neuro->getClass()->getVisualHints() & NeuroClass::AtFirstPart)
524                        pos = ((MechPart*)jo->part1->userdata[CreatMechObject::modeltags_id])->p;
525                else if (neuro->getClass()->getVisualHints() & NeuroClass::AtSecondPart)
526                        pos = ((MechPart*)jo->part2->userdata[CreatMechObject::modeltags_id])->p;
527                else pos = (((MechPart*)jo->part1->userdata[CreatMechObject::modeltags_id])->p
528                        + ((MechPart*)jo->part2->userdata[CreatMechObject::modeltags_id])->p) / 2;
529                return true;
530        }
531#endif
532        return false;
533}
534
535void NeuroImpl::get_position_x(ExtValue *ret)
536{
537        Pt3D pos;
538        if (getPosition(pos)) ret->setDouble(pos.x); else ret->setEmpty();
539}
540void NeuroImpl::get_position_y(ExtValue *ret)
541{
542        Pt3D pos;
543        if (getPosition(pos)) ret->setDouble(pos.y); else ret->setEmpty();
544}
545void NeuroImpl::get_position_z(ExtValue *ret)
546{
547        Pt3D pos;
548        if (getPosition(pos)) ret->setDouble(pos.z); else ret->setEmpty();
549}
550
551
552void NeuroImpl::createFieldsObject()
553{
554        fields_param = new Param(paramentries ? paramentries : (ParamEntry*)&empty_paramtab, this, "NeuroProperties");
555        fields_object = new ExtObject(fields_param);
556}
557
558void NeuroImpl::get_fields(ExtValue *ret)
559{
560        if (!fields_object)
561                createFieldsObject();
562        ret->setObject(*fields_object);
563}
564
565void NeuroImpl::get_neurodef(ExtValue *ret)
566{
567        ret->setObject(ExtObject(&Neuro::getStaticParam(), neuro));
568}
569
570void NeuroImpl::get_classObject(ExtValue *ret)
571{
572#ifndef SDK_WITHOUT_FRAMS
573        NeuroClassExt::makeStaticObject(ret, neuroclass);
574#endif
575}
576
577NeuroImpl::~NeuroImpl()
578{
579        if (fields_param)
580        {
581                delete fields_param;
582                delete fields_object;
583        }
584}
Note: See TracBrowser for help on using the repository browser.