From a10e49cd0d8370a3e8d14d6910dd72c0d2d15875 Mon Sep 17 00:00:00 2001 From: gtw2 Date: Thu, 31 May 2018 10:52:39 -0500 Subject: [PATCH 1/2] adding pyre skeleton archetype --- src/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 36e2f33b..99606a07 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ CONFIGURE_FILE(recycle_version.h.in "${CMAKE_CURRENT_SOURCE_DIR}/recycle_version SET(CYCLUS_CUSTOM_HEADERS "recycle_version.h") +USE_CYCLUS("recycle" "pyre") + INSTALL_CYCLUS_MODULE("recycle" "" "NONE") SET(TestSource ${recycle_TEST_CC} PARENT_SCOPE) From 4dc3fa01ae5f23453f12bf5de4d797b94c217c59 Mon Sep 17 00:00:00 2001 From: gtw2 Date: Thu, 31 May 2018 10:53:28 -0500 Subject: [PATCH 2/2] adding pyre skeleton archetype --- src/pyre.cc | 367 ++++++++++++++++++++++++++++++++++++++++++ src/pyre.h | 297 ++++++++++++++++++++++++++++++++++ src/pyre_tests.cc | 47 ++++++ src/recycle_version.h | 9 ++ 4 files changed, 720 insertions(+) create mode 100644 src/pyre.cc create mode 100644 src/pyre.h create mode 100644 src/pyre_tests.cc create mode 100644 src/recycle_version.h diff --git a/src/pyre.cc b/src/pyre.cc new file mode 100644 index 00000000..c6fdc9fb --- /dev/null +++ b/src/pyre.cc @@ -0,0 +1,367 @@ +#include "pyre.h" + +using cyclus::Material; +using cyclus::Composition; +using cyclus::toolkit::ResBuf; +using cyclus::toolkit::MatVec; +using cyclus::KeyError; +using cyclus::ValueError; +using cyclus::Request; +using cyclus::CompMap; + +namespace recycle { + +Pyre::Pyre(cyclus::Context* ctx) + : cyclus::Facility(ctx), + latitude(0.0), + longitude(0.0), + coordinates(latitude, longitude) {} + +cyclus::Inventories Pyre::SnapshotInv() { + cyclus::Inventories invs; + + // these inventory names are intentionally convoluted so as to not clash + // with the user-specified stream commods that are used as the separations + // streams inventory names. + invs["leftover-inv-name"] = leftover.PopNRes(leftover.count()); + leftover.Push(invs["leftover-inv-name"]); + invs["feed-inv-name"] = feed.PopNRes(feed.count()); + feed.Push(invs["feed-inv-name"]); + + std::map >::iterator it; + for (it = streambufs.begin(); it != streambufs.end(); ++it) { + invs[it->first] = it->second.PopNRes(it->second.count()); + it->second.Push(invs[it->first]); + } + + return invs; +} + +void Pyre::InitInv(cyclus::Inventories& inv) { + leftover.Push(inv["leftover-inv-name"]); + feed.Push(inv["feed-inv-name"]); + + cyclus::Inventories::iterator it; + for (it = inv.begin(); it != inv.end(); ++it) { + streambufs[it->first].Push(it->second); + } +} + +typedef std::pair > Stream; +typedef std::map StreamSet; + +void Pyre::EnterNotify() { + cyclus::Facility::EnterNotify(); + std::map efficiency_; + + StreamSet::iterator it; + std::map::iterator it2; + + for (it = streams_.begin(); it != streams_.end(); ++it) { + std::string name = it->first; + Stream stream = it->second; + double cap = stream.first; + if (cap >= 0) { + streambufs[name].capacity(cap); + } + + for (it2 = stream.second.begin(); it2 != stream.second.end(); it2++) { + efficiency_[it2->first] += it2->second; + } + RecordPosition(); + } + + std::vector eff_pb_; + for (it2 = efficiency_.begin(); it2 != efficiency_.end(); it2++) { + if (it2->second > 1) { + eff_pb_.push_back(it2->first); + } + } + + if (eff_pb_.size() > 0) { + std::stringstream ss; + ss << "In " << prototype() << ", "; + ss << "the following nuclide(s) have a cumulative separation efficiency " + "greater than 1:"; + for (int i = 0; i < eff_pb_.size(); i++) { + ss << "\n " << eff_pb_[i]; + if (i < eff_pb_.size() - 1) { + ss << ","; + } else { + ss << "."; + } + } + + throw cyclus::ValueError(ss.str()); + } + + if (feed_commod_prefs.size() == 0) { + for (int i = 0; i < feed_commods.size(); i++) { + feed_commod_prefs.push_back(cyclus::kDefaultPref); + } + } +} + +void Pyre::Tick() { + if (feed.count() == 0) { + return; + } + double pop_qty = std::min(throughput, feed.quantity()); + Material::Ptr mat = feed.Pop(pop_qty, cyclus::eps_rsrc()); + double orig_qty = mat->quantity(); + + StreamSet::iterator it; + double maxfrac = 1; + std::map stagedsep; + for (it = streams_.begin(); it != streams_.end(); ++it) { + Stream info = it->second; + std::string name = it->first; + stagedsep[name] = SepMaterial(info.second, mat); + double frac = streambufs[name].space() / stagedsep[name]->quantity(); + if (frac < maxfrac) { + maxfrac = frac; + } + } + + std::map::iterator itf; + for (itf = stagedsep.begin(); itf != stagedsep.end(); ++itf) { + std::string name = itf->first; + Material::Ptr m = itf->second; + if (m->quantity() > 0) { + streambufs[name].Push( + mat->ExtractComp(m->quantity() * maxfrac, m->comp())); + } + } + + if (maxfrac == 1) { + if (mat->quantity() > 0) { + // unspecified separations fractions go to leftovers + leftover.Push(mat); + } + } else { // maxfrac is < 1 + // push back any leftover feed due to separated stream inv size constraints + feed.Push(mat->ExtractQty((1 - maxfrac) * orig_qty)); + if (mat->quantity() > 0) { + // unspecified separations fractions go to leftovers + leftover.Push(mat); + } + } +} + +// Note that this returns an untracked material that should just be used for +// its composition and qty - not in any real inventories, etc. +Material::Ptr SepMaterial(std::map effs, Material::Ptr mat) { + CompMap cm = mat->comp()->mass(); + cyclus::compmath::Normalize(&cm, mat->quantity()); + double tot_qty = 0; + CompMap sepcomp; + + CompMap::iterator it; + for (it = cm.begin(); it != cm.end(); ++it) { + int nuc = it->first; + int elem = (nuc / 10000000) * 10000000; + double eff = 0; + if (effs.count(nuc) > 0) { + eff = effs[nuc]; + } else if (effs.count(elem) > 0) { + eff = effs[elem]; + } else { + continue; + } + + double qty = it->second; + double sepqty = qty * eff; + sepcomp[nuc] = sepqty; + tot_qty += sepqty; + } + + Composition::Ptr c = Composition::CreateFromMass(sepcomp); + return Material::CreateUntracked(tot_qty, c); +}; + +std::set::Ptr> +Pyre::GetMatlRequests() { + using cyclus::RequestPortfolio; + std::set::Ptr> ports; + + int t = context()->time(); + int t_exit = exit_time(); + if (t_exit >= 0 && (feed.quantity() >= (t_exit - t) * throughput)) { + return ports; // already have enough feed for remainder of life + } else if (feed.space() < cyclus::eps_rsrc()) { + return ports; + } + + bool exclusive = false; + RequestPortfolio::Ptr port(new RequestPortfolio()); + + Material::Ptr m = cyclus::NewBlankMaterial(feed.space()); + if (!feed_recipe.empty()) { + Composition::Ptr c = context()->GetRecipe(feed_recipe); + m = Material::CreateUntracked(feed.space(), c); + } + + std::vector*> reqs; + for (int i = 0; i < feed_commods.size(); i++) { + std::string commod = feed_commods[i]; + double pref = feed_commod_prefs[i]; + reqs.push_back(port->AddRequest(m, this, commod, pref, exclusive)); + } + port->AddMutualReqs(reqs); + ports.insert(port); + + return ports; +} + +void Pyre::GetMatlTrades( + const std::vector >& trades, + std::vector, Material::Ptr> >& + responses) { + using cyclus::Trade; + + std::vector >::const_iterator it; + for (int i = 0; i < trades.size(); i++) { + std::string commod = trades[i].request->commodity(); + if (commod == leftover_commod) { + double amt = std::min(leftover.quantity(), trades[i].amt); + Material::Ptr m = leftover.Pop(amt, cyclus::eps_rsrc()); + responses.push_back(std::make_pair(trades[i], m)); + } else if (streambufs.count(commod) > 0) { + double amt = std::min(streambufs[commod].quantity(), trades[i].amt); + Material::Ptr m = streambufs[commod].Pop(amt, cyclus::eps_rsrc()); + responses.push_back(std::make_pair(trades[i], m)); + } else { + throw ValueError("invalid commodity " + commod + + " on trade matched to prototype " + prototype()); + } + } +} + +void Pyre::AcceptMatlTrades( + const std::vector, Material::Ptr> >& + responses) { + std::vector, + cyclus::Material::Ptr> >::const_iterator trade; + + for (trade = responses.begin(); trade != responses.end(); ++trade) { + feed.Push(trade->second); + } +} + +std::set::Ptr> Pyre::GetMatlBids( + cyclus::CommodMap::type& commod_requests) { + using cyclus::BidPortfolio; + + bool exclusive = false; + std::set::Ptr> ports; + + // bid streams + std::map >::iterator it; + for (it = streambufs.begin(); it != streambufs.end(); ++it) { + std::string commod = it->first; + std::vector*>& reqs = commod_requests[commod]; + if (reqs.size() == 0) { + continue; + } else if (streambufs[commod].quantity() < cyclus::eps_rsrc()) { + continue; + } + + MatVec mats = streambufs[commod].PopN(streambufs[commod].count()); + streambufs[commod].Push(mats); + + BidPortfolio::Ptr port(new BidPortfolio()); + + for (int j = 0; j < reqs.size(); j++) { + Request* req = reqs[j]; + double tot_bid = 0; + for (int k = 0; k < mats.size(); k++) { + Material::Ptr m = mats[k]; + tot_bid += m->quantity(); + + // this fix the problem of the cyclus exchange manager which crashes + // when a bid with a quantity <=0 is offered. + if (m->quantity() > cyclus::eps_rsrc()) { + port->AddBid(req, m, this, exclusive); + } + + if (tot_bid >= req->target()->quantity()) { + break; + } + } + } + + double tot_qty = streambufs[commod].quantity(); + cyclus::CapacityConstraint cc(tot_qty); + port->AddConstraint(cc); + ports.insert(port); + } + + // bid leftovers + std::vector*>& reqs = commod_requests[leftover_commod]; + if (reqs.size() > 0 && leftover.quantity() >= cyclus::eps_rsrc()) { + MatVec mats = leftover.PopN(leftover.count()); + leftover.Push(mats); + + BidPortfolio::Ptr port(new BidPortfolio()); + + for (int j = 0; j < reqs.size(); j++) { + Request* req = reqs[j]; + double tot_bid = 0; + for (int k = 0; k < mats.size(); k++) { + Material::Ptr m = mats[k]; + tot_bid += m->quantity(); + + // this fix the problem of the cyclus exchange manager which crashes + // when a bid with a quantity <=0 is offered. + if (m->quantity() > cyclus::eps_rsrc()) { + port->AddBid(req, m, this, exclusive); + } + + if (tot_bid >= req->target()->quantity()) { + break; + } + } + } + + cyclus::CapacityConstraint cc(leftover.quantity()); + port->AddConstraint(cc); + ports.insert(port); + } + + return ports; +} + +void Pyre::Tock() {} + +bool Pyre::CheckDecommissionCondition() { + if (leftover.count() > 0) { + return false; + } + + std::map >::iterator it; + for (it = streambufs.begin(); it != streambufs.end(); ++it) { + if (it->second.count() > 0) { + return false; + } + } + + return true; +} + +void Pyre::RecordPosition() { + std::string specification = this->spec(); + context() + ->NewDatum("AgentPosition") + ->AddVal("Spec", specification) + ->AddVal("Prototype", this->prototype()) + ->AddVal("AgentId", id()) + ->AddVal("Latitude", latitude) + ->AddVal("Longitude", longitude) + ->Record(); +} + +extern "C" cyclus::Agent* ConstructPyre(cyclus::Context* ctx) { + return new Pyre(ctx); +} + +} // namespace cycamore diff --git a/src/pyre.h b/src/pyre.h new file mode 100644 index 00000000..1ba7aeec --- /dev/null +++ b/src/pyre.h @@ -0,0 +1,297 @@ +#ifndef RECYCLE_SRC_PYRE_H_ +#define RECYCLE_SRC_PYRE_H_ + +#include "cyclus.h" +#include "recycle_version.h" + +namespace recycle { + +/// SepMaterial returns a material object that represents the composition and +/// quantity resulting from the separation of material from mat using the given +/// mass-based efficiencies. Each key in effs represents a nuclide or element +/// (canonical PyNE form), and each value is the corresponding mass-based +/// separations efficiency for that nuclide or element. Note that this returns +/// an untracked material that should only be used for its composition and qty +/// - not in any real inventories, etc. +cyclus::Material::Ptr SepMaterial(std::map effs, + cyclus::Material::Ptr mat); + +/// Separations processes feed material into one or more streams containing +/// specific elements and/or nuclides. It uses mass-based efficiencies. +/// +/// User defined separations streams are specified as groups of +/// component-efficiency pairs where 'component' means either a particular +/// element or a particular nuclide. Each component's paired efficiency +/// represents the mass fraction of that component in the feed that is +/// separated into that stream. The efficiencies of a particular component +/// across all streams must sum up to less than or equal to one. If less than +/// one, the remainining material is sent to a waste inventory and +/// (potentially) traded away from there. +/// +/// The facility receives material into a feed inventory that it processes with +/// a specified throughput each time step. Each output stream has a +/// corresponding output inventory size/limit. If the facility is unable to +/// reduce its stocks by trading and hits this limit for any of its output +/// streams, further processing/separations of feed material will halt until +/// room is again available in the output streams. +class Pyre + : public cyclus::Facility, + public cyclus::toolkit::Position { +#pragma cyclus note { \ + "niche": "separations", \ + "doc": \ + "Separations processes feed material into one or more streams containing" \ + " specific elements and/or nuclides. It uses mass-based efficiencies." \ + "\n\n" \ + "User defined separations streams are specified as groups of" \ + " component-efficiency pairs where 'component' means either a particular" \ + " element or a particular nuclide. Each component's paired efficiency" \ + " represents the mass fraction of that component in the feed that is" \ + " separated into that stream. The efficiencies of a particular component" \ + " across all streams must sum up to less than or equal to one. If less than" \ + " one, the remainining material is sent to a waste inventory and" \ + " (potentially) traded away from there." \ + "\n\n" \ + "The facility receives material into a feed inventory that it processes with" \ + " a specified throughput each time step. Each output stream has a" \ + " corresponding output inventory size/limit. If the facility is unable to" \ + " reduce its stocks by trading and hits this limit for any of its output" \ + " streams, further processing/separations of feed material will halt until" \ + " room is again available in the output streams." \ + "", \ +} + public: + Pyre(cyclus::Context* ctx); + virtual ~Pyre(){}; + + virtual std::string version() { return RECYCLE_VERSION; } + + virtual void Tick(); + virtual void Tock(); + virtual void EnterNotify(); + + virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses); + + virtual std::set::Ptr> + GetMatlRequests(); + + virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); + + virtual void GetMatlTrades( + const std::vector >& trades, + std::vector, + cyclus::Material::Ptr> >& responses); + + virtual bool CheckDecommissionCondition(); + + #pragma cyclus clone + #pragma cyclus initfromcopy + #pragma cyclus infiletodb + #pragma cyclus initfromdb + #pragma cyclus schema + #pragma cyclus annotations + #pragma cyclus snapshot + // the following pragmas are ommitted and the functions are written + // manually in order to handle the vector of resource buffers: + // + // #pragma cyclus snapshotinv + // #pragma cyclus initinv + + virtual cyclus::Inventories SnapshotInv(); + virtual void InitInv(cyclus::Inventories& inv); + + private: + #pragma cyclus var { \ + "doc": "Ordered list of commodities on which to request feed material to " \ + "separate. Order only matters for matching up with feed commodity " \ + "preferences if specified.", \ + "uilabel": "Feed Commodity List", \ + "uitype": ["oneormore", "incommodity"], \ + } + std::vector feed_commods; + + #pragma cyclus var { \ + "default": [], \ + "uilabel": "Feed Commodity Preference List", \ + "doc": "Feed commodity request preferences for each of the given feed " \ + "commodities (same order)." \ + " If unspecified, default is to use 1.0 for all "\ + "preferences.", \ + } + std::vector feed_commod_prefs; + + #pragma cyclus var { \ + "doc": "Name for recipe to be used in feed requests." \ + " Empty string results in use of a dummy recipe.", \ + "uilabel": "Feed Commodity Recipe List", \ + "uitype": "inrecipe", \ + "default": "", \ + } + std::string feed_recipe; + + #pragma cyclus var { \ + "doc": "Amount of time spent in subprocess", \ + "tooltip": "Amount of time spent in subprocess", \ + "units": "months", \ + "uilabel": "Reprocess Time" \ + } + int reprocess_time; + + #pragma cyclus var { \ + "doc": "Volume of the subprocess container", \ + "tooltip": "Volume of the subprocess", \ + "units": "m^3", \ + "uilabel": "Volume" \ + } + double volume; + + #pragma cyclus var { \ + "doc": "Weight percent of lithium oxide added as catalyst", \ + "tooltip": "Weight percent of lithium oxide", \ + "units": "percent", \ + "uilabel": "Lithium Oxide" \ + } + double lithium_oxide; + + #pragma cyclus var { \ + "doc": "Batch size of the subprocess", \ + "tooltip": "Batch size of the subprocess", \ + "units": "kg", \ + "uilabel": "Batch Size" \ + } + double batch_size; + + #pragma cyclus var { \ + "doc": "Pressure in the electrorefining process", \ + "tooltip": "Pressure in the electrorefining process", \ + "units": "mTorr", \ + "uilabel": "Refiner Pressure" \ + } + double pressure; + + #pragma cyclus var { \ + "doc": "Temperature of the subprocess", \ + "tooltip": "Temperature of the subprocess", \ + "units": "K", \ + "uilabel": "Temperature" \ + } + double temperature; + + #pragma cyclus var { \ + "doc": "Refiner stirrer speed", \ + "tooltip": "Refiner stirrer speed", \ + "units": "rpm", \ + "uilabel": "Stirrer Speed" \ + } + double stirrer_speed; + + #pragma cyclus var { \ + "doc": "Current through the subprocess", \ + "tooltip": "Current through the subprocess", \ + "units": "mA", \ + "uilabel": "Current" \ + } + double current; + + #pragma cyclus var { \ + "doc" : "Maximum amount of feed material to keep on hand.", \ + "uilabel": "Maximum Feed Inventory", \ + "units" : "kg", \ + } + double feedbuf_size; + + #pragma cyclus var { \ + "capacity" : "feedbuf_size", \ + } + cyclus::toolkit::ResBuf feed; + + #pragma cyclus var { \ + "doc" : "Maximum quantity of feed material that can be processed per time "\ + "step.", \ + "uilabel": "Maximum Separations Throughput", \ + "default": 1e299, \ + "uitype": "range", \ + "range": [0.0, 1e299], \ + "units": "kg/(time step)", \ + } + double throughput; + + #pragma cyclus var { \ + "doc": "Commodity on which to trade the leftover separated material " \ + "stream. This MUST NOT be the same as any commodity used to define "\ + "the other separations streams.", \ + "uitype": "outcommodity", \ + "uilabel": "Leftover Commodity", \ + "default": "default-waste-stream", \ + } + std::string leftover_commod; + + #pragma cyclus var { \ + "doc" : "Maximum amount of leftover separated material (not included in" \ + " any other stream) that can be stored." \ + " If full, the facility halts operation until space becomes " \ + "available.", \ + "uilabel": "Maximum Leftover Inventory", \ + "default": 1e299, \ + "uitype": "range", \ + "range": [0.0, 1e299], \ + "units": "kg", \ + } + double leftoverbuf_size; + + #pragma cyclus var { \ + "capacity" : "leftoverbuf_size", \ + } + cyclus::toolkit::ResBuf leftover; + + #pragma cyclus var { \ + "alias": ["streams", "commod", ["info", "buf_size", ["efficiencies", "comp", "eff"]]], \ + "uitype": ["oneormore", "outcommodity", ["pair", "double", ["oneormore", "nuclide", "double"]]], \ + "uilabel": "Separations Streams and Efficiencies", \ + "doc": "Output streams for separations." \ + " Each stream must have a unique name identifying the commodity on "\ + " which its material is traded," \ + " a max buffer capacity in kg (neg values indicate infinite size)," \ + " and a set of component efficiencies." \ + " 'comp' is a component to be separated into the stream" \ + " (e.g. U, Pu, etc.) and 'eff' is the mass fraction of the" \ + " component that is separated from the feed into this output" \ + " stream. If any stream buffer is full, the facility halts" \ + " operation until space becomes available." \ + " The sum total of all component efficiencies across streams must" \ + " be less than or equal to 1" \ + " (e.g. sum of U efficiencies for all streams must be <= 1).", \ + } + std::map > > streams_; + + // custom SnapshotInv and InitInv and EnterNotify are used to persist this + // state var. + std::map > streambufs; + + #pragma cyclus var { \ + "default": 0.0, \ + "uilabel": "Geographical latitude in degrees as a double", \ + "doc": "Latitude of the agent's geographical position. The value should " \ + "be expressed in degrees as a double." \ + } + double latitude; + + #pragma cyclus var { \ + "default": 0.0, \ + "uilabel": "Geographical longitude in degrees as a double", \ + "doc": "Longitude of the agent's geographical position. The value should " \ + "be expressed in degrees as a double." \ + } + double longitude; + + cyclus::toolkit::Position coordinates; + + /// Records an agent's latitude and longitude to the output db + void RecordPosition(); +}; + +} // namespace recycle + +#endif // RECYCLE_SRC_PYRE_H_ diff --git a/src/pyre_tests.cc b/src/pyre_tests.cc new file mode 100644 index 00000000..2dce4893 --- /dev/null +++ b/src/pyre_tests.cc @@ -0,0 +1,47 @@ +#include + +#include "pyre.h" + +#include "agent_tests.h" +#include "context.h" +#include "facility_tests.h" + +using recycle::Pyre; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class PyreTest : public ::testing::Test { + protected: + cyclus::TestContext tc; + Pyre* facility; + + virtual void SetUp() { + facility = new Pyre(tc.get()); + } + + virtual void TearDown() { + delete facility; + } +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +TEST_F(PyreTest, InitialState) { + // Test things about the initial state of the facility here +} + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Do Not Touch! Below section required for connection with Cyclus +cyclus::Agent* PyreConstructor(cyclus::Context* ctx) { + return new Pyre(ctx); +} +// Required to get functionality in cyclus agent unit tests library +#ifndef CYCLUS_AGENT_TESTS_CONNECTED +int ConnectAgentTests(); +static int cyclus_agent_tests_connected = ConnectAgentTests(); +#define CYCLUS_AGENT_TESTS_CONNECTED cyclus_agent_tests_connected +#endif // CYCLUS_AGENT_TESTS_CONNECTED +INSTANTIATE_TEST_CASE_P(PyreFac, FacilityTests, + ::testing::Values(&PyreConstructor)); +INSTANTIATE_TEST_CASE_P(PyreFac, AgentTests, + ::testing::Values(&PyreConstructor)); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/recycle_version.h b/src/recycle_version.h new file mode 100644 index 00000000..d1305ee1 --- /dev/null +++ b/src/recycle_version.h @@ -0,0 +1,9 @@ +#ifndef RECYCLE_SRC_VERSION_H_ +#define RECYCLE_SRC_VERSION_H_ + +#define RECYCLE_VERSION_MAJOR 0 +#define RECYCLE_VERSION_MINOR 0 +#define RECYCLE_VERSION_MICRO 1 +#define RECYCLE_VERSION "" + +#endif // RECYCLE_SRC_VERSION_H_