<commit_before>// Copyright (c) 2016-2018 The Syscoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <test/test_syscoin_services.h>
#include <util/time.h>
#include <rpc/server.h>
#include <services/asset.h>
#include <base58.h>
#include <chainparams.h>
#include <boost/test/unit_test.hpp>
#include <iterator>
#include <key.h>
#include <rpc/util.h>
#include <core_io.h>
using namespace std;
BOOST_GLOBAL_FIXTURE( SyscoinTestingSetup );
BOOST_FIXTURE_TEST_SUITE(syscoin_asset_allocation_tests, BasicSyscoinTestingSetup)

BOOST_AUTO_TEST_CASE(generate_asset_allocation_address_sync)
{
	UniValue r;
	printf("Running generate_asset_allocation_address_sync...\n");
	GenerateBlocks(5);
	string newaddress = GetNewFundedAddress("node1");
    BOOST_CHECK_NO_THROW(r = CallExtRPC("node1", "getnewaddress"));
    string newaddressreceiver = r.get_str();
	string guid = AssetNew("node1", newaddress, "data", "''", "8", "10000", "1000000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddressreceiver + "\\\",\\\"amount\\\":5000}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_NO_THROW(r = CallRPC("node2", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");
	StopNode("node2");
	StartNode("node2");
	GenerateBlocks(5, "node2");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_NO_THROW(r = CallRPC("node2", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");

}
BOOST_AUTO_TEST_CASE(generate_asset_allocation_lock)
{
	UniValue r;
	printf("Running generate_asset_allocation_lock...\n");
	GenerateBlocks(5);
	string txid;
	string newaddress1 = GetNewFundedAddress("node2");
	string newaddress = GetNewFundedAddress("node1", txid);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "getrawtransaction " + txid + " true"));
	UniValue voutArray = find_value(r.get_obj(), "vout");
	int vout = 0;
	CAmount nAmount = 0;
	for (unsigned int i = 0; i<voutArray.size(); i++) {
		nAmount = AmountFromValue(find_value(voutArray[i].get_obj(), "value"));
		UniValue scriptObj = find_value(voutArray[i].get_obj(), "scriptPubKey").get_obj();
		UniValue addressesArray = find_value(scriptObj, "addresses");
		for (unsigned int j = 0; j<addressesArray.size(); j++) {
			if (addressesArray[j].get_str() == newaddress) {
				vout = i;
				nAmount = AmountFromValue(find_value(voutArray[i].get_obj(), "value"));
			}
		}
	}
	// for fees;
	nAmount -= 10000;
	string strAmount = ValueFromAmount(nAmount).write();
	string voutstr = itostr(vout);
	// lock outpoint so other txs can't spend through wallet auto-selection
	BOOST_CHECK_NO_THROW(CallRPC("node1", "lockunspent false \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\""));

	string guid = AssetNew("node1", newaddress, "data", "''", "8", "1", "100000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddress + "\\\",\\\"amount\\\":1}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 1 * COIN);

	LockAssetAllocation("node1", guid, newaddress, txid, voutstr);

	// unlock now to test spending
	BOOST_CHECK_NO_THROW(CallRPC("node1", "lockunspent true \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\""));
	// cannot spend as normal through sendrawtransaction
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "createrawtransaction \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\" \"[{\\\"" + newaddress1 + "\\\":" + strAmount + "}]\"", true, false));

	string rawTx = r.get_str();
	rawTx.erase(std::remove(rawTx.begin(), rawTx.end(), '\n'), rawTx.end());
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "signrawtransactionwithwallet " + rawTx));

	string hex_str = find_value(r.get_obj(), "hex").get_str();
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "testmempoolaccept \"[\\\"" + hex_str + "\\\"]\""));
	BOOST_CHECK(find_value(r.get_array()[0].get_obj(), "allowed").get_bool());
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "sendrawtransaction " + hex_str, true, false));
	BOOST_CHECK(r.write().empty());
	string txid0 = AssetAllocationTransfer(false, "node1", guid, newaddress, "\"[{\\\"address\\\":\\\"" + newaddress1 + "\\\",\\\"amount\\\":0.11}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress1));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.11 * COIN);
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "getrawtransaction " + txid0 + " true"));
	UniValue vinArray = find_value(r.get_obj(), "vin");
	bool found = false;
	for (unsigned int i = 0; i<vinArray.size(); i++) {
		if (find_value(vinArray[i].get_obj(), "txid").get_str() == txid && find_value(vinArray[i].get_obj(), "vout").get_int() == vout) {
			found = true;
			break;
		}
	}
	BOOST_CHECK(found);

}
BOOST_AUTO_TEST_CASE(generate_asset_allocation_send_address)
{
	UniValue r;
	printf("Running generate_asset_allocation_send_address...\n");
	GenerateBlocks(5);
    GenerateBlocks(101, "node2");
	string newaddress1 = GetNewFundedAddress("node1");
    CallRPC("node2", "sendtoaddress " + newaddress1 + " 1", true, false);
    CallRPC("node2", "sendtoaddress " + newaddress1 + " 1", true, false);
	GenerateBlocks(5);
	GenerateBlocks(5, "node2");
    BOOST_CHECK_NO_THROW(r = CallExtRPC("node1", "getnewaddress"));
    string newaddress2 = r.get_str();

	string guid = AssetNew("node1", newaddress1, "data","''", "8", "1", "100000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddress1 + "\\\",\\\"amount\\\":1}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress1 ));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 1 * COIN);

	string txid0 = AssetAllocationTransfer(false, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.11}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2 ));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.11 * COIN);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	// send using zdag
	string txid1 = AssetAllocationTransfer(true, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.12}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2));
	balance = find_value(r.get_obj(), "balance_zdag");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.23 * COIN);

	// non zdag cannot be found since it was already mined, but ends up briefly in conflict state because sender is conflicted
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// first tx should have to wait 1 sec for good status
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// check just sender
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// wait for 1 second as required by unit test
	MilliSleep(1000);
	// second send
	string txid2 = AssetAllocationTransfer(true, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.13}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2 ));
	balance = find_value(r.get_obj(), "balance_zdag");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.36 * COIN);

	// sender is conflicted so txid0 is conflicted by extension even if its not found
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// first ones now OK because it was found explicitly
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// second one hasn't waited enough time yet
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// check just sender
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// wait for 1.5 second to clear minor warning status
	MilliSleep(1500);
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// check just sender as well
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// after pow, should get not found on all of them
	GenerateBlocks(1);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	// check just sender as well
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);
    ECC_Stop();
}
BOOST_AUTO_TEST_SUITE_END ()
<commit_msg>dbg<commit_after>// Copyright (c) 2016-2018 The Syscoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <test/test_syscoin_services.h>
#include <util/time.h>
#include <rpc/server.h>
#include <services/asset.h>
#include <base58.h>
#include <chainparams.h>
#include <boost/test/unit_test.hpp>
#include <iterator>
#include <key.h>
#include <rpc/util.h>
#include <core_io.h>
using namespace std;
BOOST_GLOBAL_FIXTURE( SyscoinTestingSetup );
BOOST_FIXTURE_TEST_SUITE(syscoin_asset_allocation_tests, BasicSyscoinTestingSetup)

BOOST_AUTO_TEST_CASE(generate_asset_allocation_address_sync)
{
	UniValue r;
	printf("Running generate_asset_allocation_address_sync...\n");
	GenerateBlocks(5);
	string newaddress = GetNewFundedAddress("node1");
    BOOST_CHECK_NO_THROW(r = CallExtRPC("node1", "getnewaddress"));
    string newaddressreceiver = r.get_str();
	string guid = AssetNew("node1", newaddress, "data", "''", "8", "10000", "1000000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddressreceiver + "\\\",\\\"amount\\\":5000}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_NO_THROW(r = CallRPC("node2", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");
	StopNode("node2");
	StartNode("node2");
	GenerateBlocks(5, "node2");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_NO_THROW(r = CallRPC("node2", "assetallocationinfo " + guid + " " + newaddressreceiver ));
	balance = find_value(r.get_obj(), "balance");

}
BOOST_AUTO_TEST_CASE(generate_asset_allocation_lock)
{
	UniValue r;
	printf("Running generate_asset_allocation_lock...\n");
	GenerateBlocks(5);
	string txid;
	string newaddress1 = GetNewFundedAddress("node2");
	string newaddress = GetNewFundedAddress("node1", txid);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "getrawtransaction " + txid + " true"));
	UniValue voutArray = find_value(r.get_obj(), "vout");
	int vout = 0;
	CAmount nAmount = 0;
	for (unsigned int i = 0; i<voutArray.size(); i++) {
		nAmount = AmountFromValue(find_value(voutArray[i].get_obj(), "value"));
		UniValue scriptObj = find_value(voutArray[i].get_obj(), "scriptPubKey").get_obj();
		UniValue addressesArray = find_value(scriptObj, "addresses");
		for (unsigned int j = 0; j<addressesArray.size(); j++) {
			if (addressesArray[j].get_str() == newaddress) {
				vout = i;
				nAmount = AmountFromValue(find_value(voutArray[i].get_obj(), "value"));
			}
		}
	}
	// for fees;
	nAmount -= 10000;
	string strAmount = ValueFromAmount(nAmount).write();
	string voutstr = itostr(vout);
	// lock outpoint so other txs can't spend through wallet auto-selection
	BOOST_CHECK_NO_THROW(CallRPC("node1", "lockunspent false \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\""));

	string guid = AssetNew("node1", newaddress, "data", "''", "8", "1", "100000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddress + "\\\",\\\"amount\\\":1}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 1 * COIN);

	LockAssetAllocation("node1", guid, newaddress, txid, voutstr);

	// unlock now to test spending
	BOOST_CHECK_NO_THROW(CallRPC("node1", "lockunspent true \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\""));
	// cannot spend as normal through sendrawtransaction
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "createrawtransaction \"[{\\\"txid\\\":\\\"" + txid + "\\\",\\\"vout\\\":" + voutstr + "}]\" \"[{\\\"" + newaddress1 + "\\\":" + strAmount + "}]\"", true, false));

	string rawTx = r.get_str();
	rawTx.erase(std::remove(rawTx.begin(), rawTx.end(), '\n'), rawTx.end());
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "signrawtransactionwithwallet " + rawTx));

	string hex_str = find_value(r.get_obj(), "hex").get_str();
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "testmempoolaccept \"[\\\"" + hex_str + "\\\"]\""));
	BOOST_CHECK(find_value(r.get_array()[0].get_obj(), "allowed").get_bool());
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "sendrawtransaction " + hex_str, true, false));
	printf("%s\n", r.write().c_str());
	BOOST_CHECK(r.write().empty());
	string txid0 = AssetAllocationTransfer(false, "node1", guid, newaddress, "\"[{\\\"address\\\":\\\"" + newaddress1 + "\\\",\\\"amount\\\":0.11}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress1));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.11 * COIN);
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "getrawtransaction " + txid0 + " true"));
	UniValue vinArray = find_value(r.get_obj(), "vin");
	bool found = false;
	for (unsigned int i = 0; i<vinArray.size(); i++) {
		if (find_value(vinArray[i].get_obj(), "txid").get_str() == txid && find_value(vinArray[i].get_obj(), "vout").get_int() == vout) {
			found = true;
			break;
		}
	}
	BOOST_CHECK(found);

}
BOOST_AUTO_TEST_CASE(generate_asset_allocation_send_address)
{
	UniValue r;
	printf("Running generate_asset_allocation_send_address...\n");
	GenerateBlocks(5);
    GenerateBlocks(101, "node2");
	string newaddress1 = GetNewFundedAddress("node1");
    CallRPC("node2", "sendtoaddress " + newaddress1 + " 1", true, false);
    CallRPC("node2", "sendtoaddress " + newaddress1 + " 1", true, false);
	GenerateBlocks(5);
	GenerateBlocks(5, "node2");
    BOOST_CHECK_NO_THROW(r = CallExtRPC("node1", "getnewaddress"));
    string newaddress2 = r.get_str();

	string guid = AssetNew("node1", newaddress1, "data","''", "8", "1", "100000");

	AssetSend("node1", guid, "\"[{\\\"address\\\":\\\"" + newaddress1 + "\\\",\\\"amount\\\":1}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress1 ));
	UniValue balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 1 * COIN);

	string txid0 = AssetAllocationTransfer(false, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.11}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2 ));
	balance = find_value(r.get_obj(), "balance");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.11 * COIN);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	// send using zdag
	string txid1 = AssetAllocationTransfer(true, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.12}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2));
	balance = find_value(r.get_obj(), "balance_zdag");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.23 * COIN);

	// non zdag cannot be found since it was already mined, but ends up briefly in conflict state because sender is conflicted
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// first tx should have to wait 1 sec for good status
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// check just sender
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// wait for 1 second as required by unit test
	MilliSleep(1000);
	// second send
	string txid2 = AssetAllocationTransfer(true, "node1", guid, newaddress1, "\"[{\\\"address\\\":\\\"" + newaddress2 + "\\\",\\\"amount\\\":0.13}]\"");
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationinfo " + guid + " " + newaddress2 ));
	balance = find_value(r.get_obj(), "balance_zdag");
	BOOST_CHECK_EQUAL(AssetAmountFromValue(balance, 8), 0.36 * COIN);

	// sender is conflicted so txid0 is conflicted by extension even if its not found
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// first ones now OK because it was found explicitly
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// second one hasn't waited enough time yet
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// check just sender
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_MINOR_CONFLICT);

	// wait for 1.5 second to clear minor warning status
	MilliSleep(1500);
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// check just sender as well
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_STATUS_OK);

	// after pow, should get not found on all of them
	GenerateBlocks(1);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid0));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid1));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " " + txid2));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);

	// check just sender as well
	BOOST_CHECK_NO_THROW(r = CallRPC("node1", "assetallocationsenderstatus " + guid + " " + newaddress1 + " ''"));
	BOOST_CHECK_EQUAL(find_value(r.get_obj(), "status").get_int(), ZDAG_NOT_FOUND);
    ECC_Stop();
}
BOOST_AUTO_TEST_SUITE_END ()
<|endoftext|>


<commit_before>/*
    Copyright (C) 2013  Franck Arrecot <franck.arrecot@gmail.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "mergecontactswidget.h"
#include "personpresentationwidget.h"
#include "persondata.h"
#include "personsmodel.h"
#include "duplicatesfinder.h"

#include <QObject>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>

#include <KLocalizedString>
#include <KStandardDirs>
#include <KJob>
#include <KDebug>
#include <KPluginFactory>

K_PLUGIN_FACTORY( MergeContactsWidgetFactory, registerPlugin<MergeContactsWidget>(); )
K_EXPORT_PLUGIN( MergeContactsWidgetFactory("mergecontactswidgetplugin") )

using namespace KPeople;

MergeContactsWidget::MergeContactsWidget(QWidget *parent, const QVariantList &args)
    : AbstractPersonDetailsWidget(parent)
    , m_person(0)
    , m_model(0)
    , m_containerListDetails(0)
    , m_duplicatesBuster(0)
{
    setLayout(new QVBoxLayout());

    m_mergeButton = new QPushButton(this);
    m_mergeButton->setText(i18n("Merge Suggestions"));
    m_mergeButton->setVisible(false);

    connect(m_mergeButton, SIGNAL(clicked(bool)), this, SLOT(onMergePossibilitiesButtonPressed()));
    layout()->addWidget(m_mergeButton);
}

void MergeContactsWidget::setPerson(PersonData *person)
{
    m_person = person;
    searchForDuplicates();
}

void MergeContactsWidget::setPersonsModel(PersonsModel *model)
{
    m_model = model;
    searchForDuplicates();
}

void MergeContactsWidget::fillDuplicatesWidget(const QList<QPersistentModelIndex> &duplicates)
{
    // clean the previous list
    delete m_containerListDetails;
    m_listMergeContacts.clear();

    // 1- Vertical list of person-presentation-widget : one contact, one checkbox
    m_containerListDetails = new QWidget(this);
    m_containerListDetails->setLayout(new QVBoxLayout());
    layout()->addWidget(m_containerListDetails);

    // building the new button
    QPushButton *triggerButton = new QPushButton(m_containerListDetails);
    triggerButton->setText(i18n("Merge with Selected Contacts", 0));
    connect(triggerButton, SIGNAL(clicked(bool)), this, SLOT(onMergeButtonPressed()));
    m_containerListDetails->layout()->addWidget(triggerButton);

    // building personPresentationWidget to fill up the list
    Q_FOREACH (const QPersistentModelIndex &duplicate, duplicates)
    {
        // displaying contact in a user friendly way
        kDebug() << "Name retireved form the duplicate :" << duplicate.data(Qt::DisplayRole).toString();

        QString name = duplicate.data(Qt::DisplayRole).toString();
        QPixmap avatar;
        QString avatarPath = duplicate.data(PersonsModel::PhotosRole).toString();
        if (!avatarPath.isEmpty()) {
            avatar.load(avatarPath);
        }
        if (avatar.isNull()) {
            avatar.load(KStandardDirs::locate("data", "kpeople/dummy_avatar.png"));
        }

        // memorise the link between checkbox widget and model index
        PersonPresentationWidget *myMergeContactWidget = new PersonPresentationWidget(name, avatar, m_containerListDetails);
        m_containerListDetails->layout()->addWidget(myMergeContactWidget);
        m_listMergeContacts.append(qMakePair(duplicate, myMergeContactWidget));
    }
    m_containerListDetails->setVisible(false);
}

QList<QPersistentModelIndex> MergeContactsWidget::duplicateBusterFromPerson(const QModelIndex &original)
{
    Q_ASSERT(m_duplicatesBuster);
    QList<Match> wrongFormatResults = m_duplicatesBuster->results();
    QList<QPersistentModelIndex> duplicateMatching;

    Q_FOREACH (const Match &match, wrongFormatResults) {
        // pick up only the couple with match with our parameter index
        if (match.indexA == original) {
            duplicateMatching.append(match.indexB);
        }

        if (match.indexB == original) {
            duplicateMatching.append(match.indexA);
        }
    }
    m_duplicatesBuster = 0;

    kDebug() << "Result of the duplicates Buster :" << duplicateMatching.size();
    return duplicateMatching;
}

void MergeContactsWidget::searchForDuplicates()
{
    m_mergeButton->setVisible(false);
    if (m_duplicatesBuster || !m_person || !m_person->isValid() || !m_model) {
        kDebug() << "Merge Widget failed to launch the duplicates search";
        return;
    }
    m_duplicatesBuster = new DuplicatesFinder(m_model , this);
    connect (m_duplicatesBuster, SIGNAL(result(KJob*)), SLOT(searchForDuplicatesFinished()));
    m_duplicatesBuster->start();
}

void MergeContactsWidget::searchForDuplicatesFinished()
{
    kDebug() << "Result From duplicatesFinder Available!" ;
    QModelIndex index = m_model->indexForUri( m_person->uri() );
    QList<QPersistentModelIndex> duplicates = duplicateBusterFromPerson(index);

    if (!duplicates.isEmpty()) {
        m_mergeButton->setVisible(true);
        fillDuplicatesWidget(duplicates);
    }
}

void MergeContactsWidget::onMergePossibilitiesButtonPressed()
{
    m_mergeButton->setVisible(false);
    m_containerListDetails->setVisible(true);
}

void MergeContactsWidget::onMergeButtonPressed()
{
    // Retrieve the selected contacts
    QList<QPersistentModelIndex> indexesToMerge = getContactsCheckedToMerge();
    QModelIndex index = m_model->indexForUri(m_person->uri());
    indexesToMerge.prepend(index);

    // do the merge
    QList<QUrl> urisToMergeConverted;
    Q_FOREACH (const QPersistentModelIndex &pIndex, indexesToMerge) {
        urisToMergeConverted << pIndex.data(KPeople::PersonsModel::UriRole).toUrl();
    }
    //commented because it removes the test cases
    m_model->createPersonFromUris(urisToMergeConverted);

    searchForDuplicates();
}

QList<QPersistentModelIndex> MergeContactsWidget::getContactsCheckedToMerge()
{
    QList<QPersistentModelIndex> indexesToMerge;

    // retrieve all the widget where the box is checked
    QPair<QPersistentModelIndex, PersonPresentationWidget*> mergeContact ;
    Q_FOREACH (mergeContact, m_listMergeContacts) {
        if (mergeContact.second->isContactSelected()) {
            indexesToMerge.append(mergeContact.first);
        }
    }
    kDebug() << "Amount of checked box enable :" << indexesToMerge.size();
    return indexesToMerge;
}
<commit_msg>Change the way to clean the content displayed for the merge widget<commit_after>/*
    Copyright (C) 2013  Franck Arrecot <franck.arrecot@gmail.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "mergecontactswidget.h"
#include "personpresentationwidget.h"
#include "persondata.h"
#include "personsmodel.h"
#include "duplicatesfinder.h"

#include <QObject>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>

#include <KLocalizedString>
#include <KStandardDirs>
#include <KJob>
#include <KDebug>
#include <KPluginFactory>

K_PLUGIN_FACTORY( MergeContactsWidgetFactory, registerPlugin<MergeContactsWidget>(); )
K_EXPORT_PLUGIN( MergeContactsWidgetFactory("mergecontactswidgetplugin") )

using namespace KPeople;

MergeContactsWidget::MergeContactsWidget(QWidget *parent, const QVariantList &args)
    : AbstractPersonDetailsWidget(parent)
    , m_person(0)
    , m_model(0)
    , m_containerListDetails(0)
    , m_duplicatesBuster(0)
{
    setLayout(new QVBoxLayout());
    m_mergeButton = new QPushButton(this);
    m_mergeButton->setText(i18n("Merge Suggestions"));
    m_mergeButton->setVisible(false);

    connect(m_mergeButton, SIGNAL(clicked(bool)), this, SLOT(onMergePossibilitiesButtonPressed()));
    layout()->addWidget(m_mergeButton);
}

void MergeContactsWidget::setPerson(PersonData *person)
{
    m_person = person;
    searchForDuplicates();
}

void MergeContactsWidget::setPersonsModel(PersonsModel *model)
{
    m_model = model;
    searchForDuplicates();
}

void MergeContactsWidget::fillDuplicatesWidget(const QList<QPersistentModelIndex> &duplicates)
{
    // clean the previous list
    delete m_containerListDetails;
    m_listMergeContacts.clear();

    // 1- Vertical list of person-presentation-widget : one contact, one checkbox
    m_containerListDetails = new QWidget(this);
    m_containerListDetails->setLayout(new QVBoxLayout());
    layout()->addWidget(m_containerListDetails);
    m_containerListDetails->setVisible(false);

    if (!duplicates.size()) {
        return ;
    }

    // building the new button
    QPushButton *triggerButton = new QPushButton(m_containerListDetails);
    triggerButton->setText(i18n("Merge with Selected Contacts", 0));
    connect(triggerButton, SIGNAL(clicked(bool)), this, SLOT(onMergeButtonPressed()));
    m_containerListDetails->layout()->addWidget(triggerButton);

    // building personPresentationWidget to fill up the list
    Q_FOREACH (const QPersistentModelIndex &duplicate, duplicates)
    {
        // displaying contact in a user friendly way
        kDebug() << "Name retireved form the duplicate :" << duplicate.data(Qt::DisplayRole).toString();

        QString name = duplicate.data(Qt::DisplayRole).toString();
        QPixmap avatar;
        QString avatarPath = duplicate.data(PersonsModel::PhotosRole).toString();
        if (!avatarPath.isEmpty()) {
            avatar.load(avatarPath);
        }
        if (avatar.isNull()) {
            avatar.load(KStandardDirs::locate("data", "kpeople/dummy_avatar.png"));
        }

        // memorise the link between checkbox widget and model index
        PersonPresentationWidget *myMergeContactWidget = new PersonPresentationWidget(name, avatar, m_containerListDetails);
        m_containerListDetails->layout()->addWidget(myMergeContactWidget);
        m_listMergeContacts.append(qMakePair(duplicate, myMergeContactWidget));
    }
}

QList<QPersistentModelIndex> MergeContactsWidget::duplicateBusterFromPerson(const QModelIndex &original)
{
    Q_ASSERT(m_duplicatesBuster);
    QList<Match> wrongFormatResults = m_duplicatesBuster->results();
    QList<QPersistentModelIndex> duplicateMatching;

    Q_FOREACH (const Match &match, wrongFormatResults) {
        // pick up only the couple with match with our parameter index
        if (match.indexA == original) {
            duplicateMatching.append(match.indexB);
        }

        if (match.indexB == original) {
            duplicateMatching.append(match.indexA);
        }
    }
    m_duplicatesBuster = 0;

    kDebug() << "Result of the duplicates Buster :" << duplicateMatching.size();
    return duplicateMatching;
}

void MergeContactsWidget::searchForDuplicates()
{
    m_mergeButton->setVisible(false);
    if (m_duplicatesBuster || !m_person || !m_person->isValid() || !m_model) {
        kDebug() << "Merge Widget failed to launch the duplicates search";
        return;
    }
    m_duplicatesBuster = new DuplicatesFinder(m_model , this);
    connect (m_duplicatesBuster, SIGNAL(result(KJob*)), SLOT(searchForDuplicatesFinished()));
    m_duplicatesBuster->start();
}

void MergeContactsWidget::searchForDuplicatesFinished()
{
    kDebug() << "Result From duplicatesFinder Available!" ;
    QModelIndex index = m_model->indexForUri( m_person->uri() );
    QList<QPersistentModelIndex> duplicates = duplicateBusterFromPerson(index);

    m_mergeButton->setVisible(!duplicates.isEmpty());
    fillDuplicatesWidget(duplicates);
}

void MergeContactsWidget::onMergePossibilitiesButtonPressed()
{
    m_mergeButton->setVisible(false);
    m_containerListDetails->setVisible(true);
}

void MergeContactsWidget::onMergeButtonPressed()
{
    // Retrieve the selected contacts
    QList<QPersistentModelIndex> indexesToMerge = getContactsCheckedToMerge();
    QModelIndex index = m_model->indexForUri(m_person->uri());
    indexesToMerge.prepend(index);

    // do the merge
    QList<QUrl> urisToMergeConverted;
    Q_FOREACH (const QPersistentModelIndex &pIndex, indexesToMerge) {
        urisToMergeConverted << pIndex.data(KPeople::PersonsModel::UriRole).toUrl();
    }
    //commented because it removes the test cases
    m_model->createPersonFromUris(urisToMergeConverted);

    searchForDuplicates();
}

QList<QPersistentModelIndex> MergeContactsWidget::getContactsCheckedToMerge()
{
    QList<QPersistentModelIndex> indexesToMerge;

    // retrieve all the widget where the box is checked
    QPair<QPersistentModelIndex, PersonPresentationWidget*> mergeContact ;
    Q_FOREACH (mergeContact, m_listMergeContacts) {
        if (mergeContact.second->isContactSelected()) {
            indexesToMerge.append(mergeContact.first);
        }
    }
    kDebug() << "Amount of checked box enable :" << indexesToMerge.size();
    return indexesToMerge;
}
<|endoftext|>


<commit_before>#ifndef STAN_MATH_PRIM_CORE_INIT_THREADPOOL_TBB_HPP
#define STAN_MATH_PRIM_CORE_INIT_THREADPOOL_TBB_HPP

#include <stan/math/prim/err/invalid_argument.hpp>

#include <boost/lexical_cast.hpp>

#ifndef TBB_INTERFACE_NEW
#include <tbb/tbb_stddef.h>

#if TBB_VERSION_MAJOR >= 2020
#ifndef TBB_INTERFACE_NEW
#define TBB_INTERFACE_NEW
#endif
#endif
#endif

#ifdef TBB_INTERFACE_NEW
#include <tbb/global_control.h>
#include <tbb/task_arena.h>
#else
#include <tbb/task_scheduler_init.h>
#endif

#include <cstdlib>
#include <thread>

namespace stan {
namespace math {
namespace internal {

/**
 * Get number of threads to use. The function uses the environment
 * variable STAN_NUM_THREADS and follows these conventions:
 *
 * - STAN_NUM_THREADS is not defined => num_threads=1
 * - STAN_NUM_THREADS is positive => num_threads is set to the
 *   specified number
 * - STAN_NUM_THREADS is set to -1 => num_threads is the number of
 *   available cores on the machine
 * - STAN_NUM_THREADS < -1, STAN_NUM_THREADS = 0 or STAN_NUM_THREADS is
 *   not numeric => throws an exception
 *
 * @return number of threads to use
 * @throws std::invalid_argument if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline int get_num_threads() {
  int num_threads = 1;
  const char* env_stan_num_threads = std::getenv("STAN_NUM_THREADS");
  if (env_stan_num_threads != nullptr) {
    try {
      const int env_num_threads
          = boost::lexical_cast<int>(env_stan_num_threads);
      if (env_num_threads > 0) {
        num_threads = env_num_threads;
      } else if (env_num_threads == -1) {
        num_threads = std::thread::hardware_concurrency();
      } else {
        invalid_argument("get_num_threads(int)", "STAN_NUM_THREADS",
                         env_stan_num_threads,
                         "The STAN_NUM_THREADS environment variable is '",
                         "' but it must be positive or -1");
      }
    } catch (const boost::bad_lexical_cast&) {
      invalid_argument("get_num_threads(int)", "STAN_NUM_THREADS",
                       env_stan_num_threads,
                       "The STAN_NUM_THREADS environment variable is '",
                       "' but it must be a positive number or -1");
    }
  }
  return num_threads;
}

}  // namespace internal

#ifdef TBB_INTERFACE_NEW
/**
 * Initialize the Intel TBB threadpool and global scheduler through
 * the tbb::task_arena object. In case an instance of the
 * tbb::task_scheduler_object has been instantiated prior to calling
 * this function, then any subsequent initialization is ignored by the
 * Intel TBB.
 *
 * The maximal number of threads is read from the environment variable
 * STAN_NUM_THREADS using internal::get_num_threads. See conventions
 * of get_num_threads. The TBB scheduler will be activated by calling
 * this function.
 *
 * The function returns a reference to the static
 * tbb::global_control instance.
 *
 * @return reference to the static tbb::global_control
 * @throws std::runtime_error if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline tbb::task_arena& init_threadpool_tbb() {
  int tbb_max_threads = internal::get_num_threads();
  static tbb::global_control tbb_gc(
      tbb::global_control::max_allowed_parallelism, tbb_max_threads);

  static tbb::task_arena tbb_arena(tbb_max_threads, 1);
  tbb_arena.initialize();

  return tbb_arena;
}
#else
/**
 * Initialize the Intel TBB threadpool and global scheduler through
 * the tbb::task_scheduler_init object. In case an instance of the
 * tbb::task_scheduler_object has been instantiated prior to calling
 * this function, then any subsequent initialization is ignored by the
 * Intel TBB.
 *
 * The maximal number of threads is read from the environment variable
 * STAN_NUM_THREADS using internal::get_num_threads. See conventions
 * of get_num_threads. The TBB scheduler will be activated by calling
 * this function.
 *
 * The function returns a reference to the static
 * tbb::task_scheduler_init instance.
 *
 * @param stack_size sets the stack size of each thread; the default 0
 * let's the TBB choose the stack size
 * @return reference to the static tbb::task_scheduler_init
 * @throws std::runtime_error if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline tbb::task_scheduler_init& init_threadpool_tbb(
    tbb::stack_size_type stack_size = 0) {
  int tbb_max_threads = internal::get_num_threads();

  static tbb::task_scheduler_init tbb_scheduler(tbb_max_threads, stack_size);

  return tbb_scheduler;
}
#endif

}  // namespace math
}  // namespace stan

#endif
<commit_msg>Remove extraneous if condition<commit_after>#ifndef STAN_MATH_PRIM_CORE_INIT_THREADPOOL_TBB_HPP
#define STAN_MATH_PRIM_CORE_INIT_THREADPOOL_TBB_HPP

#include <stan/math/prim/err/invalid_argument.hpp>

#include <boost/lexical_cast.hpp>

#ifndef TBB_INTERFACE_NEW
#include <tbb/tbb_stddef.h>

#if TBB_VERSION_MAJOR >= 2020
#define TBB_INTERFACE_NEW
#endif
#endif

#ifdef TBB_INTERFACE_NEW
#include <tbb/global_control.h>
#include <tbb/task_arena.h>
#else
#include <tbb/task_scheduler_init.h>
#endif

#include <cstdlib>
#include <thread>

namespace stan {
namespace math {
namespace internal {

/**
 * Get number of threads to use. The function uses the environment
 * variable STAN_NUM_THREADS and follows these conventions:
 *
 * - STAN_NUM_THREADS is not defined => num_threads=1
 * - STAN_NUM_THREADS is positive => num_threads is set to the
 *   specified number
 * - STAN_NUM_THREADS is set to -1 => num_threads is the number of
 *   available cores on the machine
 * - STAN_NUM_THREADS < -1, STAN_NUM_THREADS = 0 or STAN_NUM_THREADS is
 *   not numeric => throws an exception
 *
 * @return number of threads to use
 * @throws std::invalid_argument if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline int get_num_threads() {
  int num_threads = 1;
  const char* env_stan_num_threads = std::getenv("STAN_NUM_THREADS");
  if (env_stan_num_threads != nullptr) {
    try {
      const int env_num_threads
          = boost::lexical_cast<int>(env_stan_num_threads);
      if (env_num_threads > 0) {
        num_threads = env_num_threads;
      } else if (env_num_threads == -1) {
        num_threads = std::thread::hardware_concurrency();
      } else {
        invalid_argument("get_num_threads(int)", "STAN_NUM_THREADS",
                         env_stan_num_threads,
                         "The STAN_NUM_THREADS environment variable is '",
                         "' but it must be positive or -1");
      }
    } catch (const boost::bad_lexical_cast&) {
      invalid_argument("get_num_threads(int)", "STAN_NUM_THREADS",
                       env_stan_num_threads,
                       "The STAN_NUM_THREADS environment variable is '",
                       "' but it must be a positive number or -1");
    }
  }
  return num_threads;
}

}  // namespace internal

#ifdef TBB_INTERFACE_NEW
/**
 * Initialize the Intel TBB threadpool and global scheduler through
 * the tbb::task_arena object. In case an instance of the
 * tbb::task_scheduler_object has been instantiated prior to calling
 * this function, then any subsequent initialization is ignored by the
 * Intel TBB.
 *
 * The maximal number of threads is read from the environment variable
 * STAN_NUM_THREADS using internal::get_num_threads. See conventions
 * of get_num_threads. The TBB scheduler will be activated by calling
 * this function.
 *
 * The function returns a reference to the static
 * tbb::global_control instance.
 *
 * @return reference to the static tbb::global_control
 * @throws std::runtime_error if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline tbb::task_arena& init_threadpool_tbb() {
  int tbb_max_threads = internal::get_num_threads();
  static tbb::global_control tbb_gc(
      tbb::global_control::max_allowed_parallelism, tbb_max_threads);

  static tbb::task_arena tbb_arena(tbb_max_threads, 1);
  tbb_arena.initialize();

  return tbb_arena;
}
#else
/**
 * Initialize the Intel TBB threadpool and global scheduler through
 * the tbb::task_scheduler_init object. In case an instance of the
 * tbb::task_scheduler_object has been instantiated prior to calling
 * this function, then any subsequent initialization is ignored by the
 * Intel TBB.
 *
 * The maximal number of threads is read from the environment variable
 * STAN_NUM_THREADS using internal::get_num_threads. See conventions
 * of get_num_threads. The TBB scheduler will be activated by calling
 * this function.
 *
 * The function returns a reference to the static
 * tbb::task_scheduler_init instance.
 *
 * @param stack_size sets the stack size of each thread; the default 0
 * let's the TBB choose the stack size
 * @return reference to the static tbb::task_scheduler_init
 * @throws std::runtime_error if the value of STAN_NUM_THREADS env. variable
 * is invalid
 */
inline tbb::task_scheduler_init& init_threadpool_tbb(
    tbb::stack_size_type stack_size = 0) {
  int tbb_max_threads = internal::get_num_threads();

  static tbb::task_scheduler_init tbb_scheduler(tbb_max_threads, stack_size);

  return tbb_scheduler;
}
#endif

}  // namespace math
}  // namespace stan

#endif
<|endoftext|>


<commit_before>/// \file ROOT/RPageStorage.hxx
/// \ingroup NTuple ROOT7
/// \author Jakob Blomer <jblomer@cern.ch>
/// \date 2018-07-19
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
/// is welcome!

/*************************************************************************
 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#ifndef ROOT7_RPageStorage
#define ROOT7_RPageStorage

#include <ROOT/RNTupleDescriptor.hxx>
#include <ROOT/RNTupleOptions.hxx>
#include <ROOT/RNTupleUtil.hxx>
#include <ROOT/RPage.hxx>
#include <ROOT/RPageAllocator.hxx>
#include <ROOT/RStringView.hxx>

#include <atomic>
#include <cstddef>
#include <functional>
#include <memory>
#include <unordered_set>

namespace ROOT {
namespace Experimental {

class RNTupleModel;
// TODO(jblomer): factory methods to create tree sinks and sources outside Detail namespace

namespace Detail {

class RCluster;
class RColumn;
class RColumnElementBase;
class RNTupleCompressor;
class RNTupleDecompressor;
class RPagePool;
class RFieldBase;
class RNTupleMetrics;

enum class EPageStorageType {
   kSink,
   kSource,
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageStorage
\ingroup NTuple
\brief Common functionality of an ntuple storage for both reading and writing

The RPageStore provides access to a storage container that keeps the bits of pages and clusters comprising
an ntuple.  Concrete implementations can use a TFile, a raw file, an object store, and so on.
*/
// clang-format on
class RPageStorage {
public:
   /// The interface of a task scheduler to schedule page (de)compression tasks
   class RTaskScheduler {
   public:
      virtual ~RTaskScheduler() = default;
      /// Start a new set of tasks
      virtual void Reset() = 0;
      /// Take a callable that represents a task
      virtual void AddTask(const std::function<void(void)> &taskFunc) = 0;
      /// Blocks until all scheduled tasks finished
      virtual void Wait() = 0;
   };

   /// A sealed page contains the bytes of a page as written to storage (packed & compressed).  It is used
   /// as an input to UnsealPages() as well as to transfer pages between different storage media.
   struct RSealedPage {
      const void *fBuffer = nullptr;
      std::uint32_t fSize = 0;
      std::uint32_t fNElements = 0;

      RSealedPage() = default;
      RSealedPage(const RSealedPage &other) = delete;
      RSealedPage& operator =(const RSealedPage &other) = delete;
      RSealedPage(RSealedPage &&other) = default;
      RSealedPage& operator =(RSealedPage &&other) = default;
   };

protected:
   std::string fNTupleName;
   RTaskScheduler *fTaskScheduler = nullptr;

public:
   explicit RPageStorage(std::string_view name);
   RPageStorage(const RPageStorage &other) = delete;
   RPageStorage& operator =(const RPageStorage &other) = delete;
   RPageStorage(RPageStorage &&other) = default;
   RPageStorage& operator =(RPageStorage &&other) = default;
   virtual ~RPageStorage();

   /// Whether the concrete implementation is a sink or a source
   virtual EPageStorageType GetType() = 0;

   struct RColumnHandle {
      DescriptorId_t fId = kInvalidDescriptorId;
      const RColumn *fColumn = nullptr;

      /// Returns true for a valid column handle; fColumn and fId should always either both
      /// be valid or both be invalid.
      operator bool() const { return fId != kInvalidDescriptorId && fColumn; }
   };
   /// The column handle identifies a column with the current open page storage
   using ColumnHandle_t = RColumnHandle;

   /// Register a new column.  When reading, the column must exist in the ntuple on disk corresponding to the meta-data.
   /// When writing, every column can only be attached once.
   virtual ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) = 0;
   /// Unregisters a column.  A page source decreases the reference counter for the corresponding active column.
   /// For a page sink, dropping columns is currently a no-op.
   virtual void DropColumn(ColumnHandle_t columnHandle) = 0;

   /// Every page store needs to be able to free pages it handed out.  But Sinks and sources have different means
   /// of allocating pages.
   virtual void ReleasePage(RPage &page) = 0;

   /// Returns an empty metrics.  Page storage implementations usually have their own metrics.
   virtual RNTupleMetrics &GetMetrics();

   void SetTaskScheduler(RTaskScheduler *taskScheduler) { fTaskScheduler = taskScheduler; }
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageSink
\ingroup NTuple
\brief Abstract interface to write data into an ntuple

The page sink takes the list of columns and afterwards a series of page commits and cluster commits.
The user is responsible to commit clusters at a consistent point, i.e. when all pages corresponding to data
up to the given entry number are committed.
*/
// clang-format on
class RPageSink : public RPageStorage {
protected:
   RNTupleWriteOptions fOptions;

   /// Helper to zip pages and header/footer; comprises a 16MB unzip buffer.
   /// There could be concrete page sinks that don't need a compressor.  Therefore, and in order to stay consistent
   /// with the page source, we leave it up to the derived class whether or not the compressor gets constructed.
   std::unique_ptr<RNTupleCompressor> fCompressor;

   /// Building the ntuple descriptor while writing is done in the same way for all the storage sink implementations.
   /// Field, column, cluster ids and page indexes per cluster are issued sequentially starting with 0
   DescriptorId_t fLastFieldId = 0;
   DescriptorId_t fLastColumnId = 0;
   DescriptorId_t fLastClusterId = 0;
   NTupleSize_t fPrevClusterNEntries = 0;
   /// Keeps track of the number of elements in the currently open cluster. Indexed by column id.
   std::vector<RClusterDescriptor::RColumnRange> fOpenColumnRanges;
   /// Keeps track of the written pages in the currently open cluster. Indexed by column id.
   std::vector<RClusterDescriptor::RPageRange> fOpenPageRanges;
   RNTupleDescriptorBuilder fDescriptorBuilder;

   virtual void CreateImpl(const RNTupleModel &model) = 0;
   virtual RClusterDescriptor::RLocator CommitPageImpl(ColumnHandle_t columnHandle, const RPage &page) = 0;
   virtual RClusterDescriptor::RLocator CommitClusterImpl(NTupleSize_t nEntries) = 0;
   virtual void CommitDatasetImpl() = 0;

   /// Helper for streaming a page. This is commonly used in derived, concrete page sinks. Note that if
   /// compressionSetting is 0 (uncompressed) and the page is mappable, the returned sealed page will
   /// point directly to the input page buffer.  Otherwise, the sealed page references an internal buffer
   /// of fCompressor.  Thus, the buffer pointed to by sealed page should never be freed.
   /// Usage of this method requires construction of fCompressor.
   RSealedPage SealPage(const RPage &page, const RColumnElementBase &element, int compressionSetting);

public:
   RPageSink(std::string_view ntupleName, const RNTupleWriteOptions &options);

   RPageSink(const RPageSink&) = delete;
   RPageSink& operator=(const RPageSink&) = delete;
   RPageSink(RPageSink&&) = default;
   RPageSink& operator=(RPageSink&&) = default;
   virtual ~RPageSink();

   /// Guess the concrete derived page source from the file name (location)
   static std::unique_ptr<RPageSink> Create(std::string_view ntupleName, std::string_view location,
                                            const RNTupleWriteOptions &options = RNTupleWriteOptions());
   EPageStorageType GetType() final { return EPageStorageType::kSink; }

   ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) final;
   void DropColumn(ColumnHandle_t /*columnHandle*/) final {}

   /// Physically creates the storage container to hold the ntuple (e.g., a keys a TFile or an S3 bucket)
   /// To do so, Create() calls CreateImpl() after updating the descriptor.
   /// Create() associates column handles to the columns referenced by the model
   void Create(RNTupleModel &model);
   /// Write a page to the storage. The column must have been added before.
   void CommitPage(ColumnHandle_t columnHandle, const RPage &page);
   /// Finalize the current cluster and create a new one for the following data.
   void CommitCluster(NTupleSize_t nEntries);
   /// Finalize the current cluster and the entrire data set.
   void CommitDataset() { CommitDatasetImpl(); }

   /// Get a new, empty page for the given column that can be filled with up to nElements.  If nElements is zero,
   /// the page sink picks an appropriate size.
   virtual RPage ReservePage(ColumnHandle_t columnHandle, std::size_t nElements = 0) = 0;
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageSource
\ingroup NTuple
\brief Abstract interface to read data from an ntuple

The page source is initialized with the columns of interest. Pages from those columns can then be
mapped into memory. The page source also gives access to the ntuple's meta-data.
*/
// clang-format on
class RPageSource : public RPageStorage {
public:
   /// Derived from the model (fields) that are actually being requested at a given point in time
   using ColumnSet_t = std::unordered_set<DescriptorId_t>;

protected:
   RNTupleReadOptions fOptions;
   RNTupleDescriptor fDescriptor;
   /// The active columns are implicitly defined by the model fields or views
   ColumnSet_t fActiveColumns;

   /// Helper to unzip pages and header/footer; comprises a 16MB unzip buffer.
   /// Not all page sources need a decompressor (e.g. virtual ones for chains and friends don't), thus we
   /// leave it up to the derived class whether or not the decompressor gets constructed.
   std::unique_ptr<RNTupleDecompressor> fDecompressor;

   virtual RNTupleDescriptor AttachImpl() = 0;
   // Only called if a task scheduler is set. No-op be default.
   virtual void UnzipClusterImpl(RCluster * /* cluster */)
      { }

   /// Helper for unstreaming a page. This is commonly used in derived, concrete page sources.  The implementation
   /// currently always makes a memory copy, even if the sealed page is uncompressed and in the final memory layout.
   /// The optimization of directly mapping pages is left to the concrete page source implementations.
   /// Usage of this method requires construction of fDecompressor.
   unsigned char *UnsealPage(const RSealedPage &sealedPage, const RColumnElementBase &element);

public:
   RPageSource(std::string_view ntupleName, const RNTupleReadOptions &fOptions);
   RPageSource(const RPageSource&) = delete;
   RPageSource& operator=(const RPageSource&) = delete;
   RPageSource(RPageSource&&) = default;
   RPageSource& operator=(RPageSource&&) = default;
   virtual ~RPageSource();
   /// Guess the concrete derived page source from the file name (location)
   static std::unique_ptr<RPageSource> Create(std::string_view ntupleName, std::string_view location,
                                              const RNTupleReadOptions &options = RNTupleReadOptions());
   /// Open the same storage multiple time, e.g. for reading in multiple threads
   virtual std::unique_ptr<RPageSource> Clone() const = 0;

   EPageStorageType GetType() final { return EPageStorageType::kSource; }
   const RNTupleDescriptor &GetDescriptor() const { return fDescriptor; }
   ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) final;
   void DropColumn(ColumnHandle_t columnHandle) final;

   /// Open the physical storage container for the tree
   void Attach() { fDescriptor = AttachImpl(); }
   NTupleSize_t GetNEntries();
   NTupleSize_t GetNElements(ColumnHandle_t columnHandle);
   ColumnId_t GetColumnId(ColumnHandle_t columnHandle);

   /// Allocates and fills a page that contains the index-th element
   virtual RPage PopulatePage(ColumnHandle_t columnHandle, NTupleSize_t globalIndex) = 0;
   /// Another version of PopulatePage that allows to specify cluster-relative indexes
   virtual RPage PopulatePage(ColumnHandle_t columnHandle, const RClusterIndex &clusterIndex) = 0;

   /// Populates all the pages of the given cluster id and columns; it is possible that some columns do not
   /// contain any pages.  The pages source may load more columns than the minimal necessary set from `columns`.
   /// To indicate which columns have been loaded, LoadCluster() must mark them with SetColumnAvailable().
   /// That includes the ones from the `columns` that don't have pages; otherwise subsequent requests
   /// for the cluster would assume an incomplete cluster and trigger loading again.
   /// LoadCluster() is typically called from the I/O thread of a cluster pool, i.e. the method runs
   /// concurrently to other methods of the page source.
   virtual std::unique_ptr<RCluster> LoadCluster(DescriptorId_t clusterId, const ColumnSet_t &columns) = 0;

   /// Parallel decompression and unpacking of the pages in the given cluster. The unzipped pages are supposed
   /// to be preloaded in a page pool attached to the source. The method is triggered by the cluster pool's
   /// unzip thread. It is an optional optimization, the method can safely do nothing. In particular, the
   /// actual implementation will only run if a task scheduler is set. In practice, a task scheduler is set
   /// if implicit multi-threading is turned on.
   void UnzipCluster(RCluster *cluster);
};

} // namespace Detail

} // namespace Experimental
} // namespace ROOT

#endif
<commit_msg>[ntuple] Improve code comment (NFC)<commit_after>/// \file ROOT/RPageStorage.hxx
/// \ingroup NTuple ROOT7
/// \author Jakob Blomer <jblomer@cern.ch>
/// \date 2018-07-19
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
/// is welcome!

/*************************************************************************
 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#ifndef ROOT7_RPageStorage
#define ROOT7_RPageStorage

#include <ROOT/RNTupleDescriptor.hxx>
#include <ROOT/RNTupleOptions.hxx>
#include <ROOT/RNTupleUtil.hxx>
#include <ROOT/RPage.hxx>
#include <ROOT/RPageAllocator.hxx>
#include <ROOT/RStringView.hxx>

#include <atomic>
#include <cstddef>
#include <functional>
#include <memory>
#include <unordered_set>

namespace ROOT {
namespace Experimental {

class RNTupleModel;
// TODO(jblomer): factory methods to create tree sinks and sources outside Detail namespace

namespace Detail {

class RCluster;
class RColumn;
class RColumnElementBase;
class RNTupleCompressor;
class RNTupleDecompressor;
class RPagePool;
class RFieldBase;
class RNTupleMetrics;

enum class EPageStorageType {
   kSink,
   kSource,
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageStorage
\ingroup NTuple
\brief Common functionality of an ntuple storage for both reading and writing

The RPageStore provides access to a storage container that keeps the bits of pages and clusters comprising
an ntuple.  Concrete implementations can use a TFile, a raw file, an object store, and so on.
*/
// clang-format on
class RPageStorage {
public:
   /// The interface of a task scheduler to schedule page (de)compression tasks
   class RTaskScheduler {
   public:
      virtual ~RTaskScheduler() = default;
      /// Start a new set of tasks
      virtual void Reset() = 0;
      /// Take a callable that represents a task
      virtual void AddTask(const std::function<void(void)> &taskFunc) = 0;
      /// Blocks until all scheduled tasks finished
      virtual void Wait() = 0;
   };

   /// A sealed page contains the bytes of a page as written to storage (packed & compressed).  It is used
   /// as an input to UnsealPages() as well as to transfer pages between different storage media.
   struct RSealedPage {
      const void *fBuffer = nullptr;
      std::uint32_t fSize = 0;
      std::uint32_t fNElements = 0;

      RSealedPage() = default;
      RSealedPage(const RSealedPage &other) = delete;
      RSealedPage& operator =(const RSealedPage &other) = delete;
      RSealedPage(RSealedPage &&other) = default;
      RSealedPage& operator =(RSealedPage &&other) = default;
   };

protected:
   std::string fNTupleName;
   RTaskScheduler *fTaskScheduler = nullptr;

public:
   explicit RPageStorage(std::string_view name);
   RPageStorage(const RPageStorage &other) = delete;
   RPageStorage& operator =(const RPageStorage &other) = delete;
   RPageStorage(RPageStorage &&other) = default;
   RPageStorage& operator =(RPageStorage &&other) = default;
   virtual ~RPageStorage();

   /// Whether the concrete implementation is a sink or a source
   virtual EPageStorageType GetType() = 0;

   struct RColumnHandle {
      DescriptorId_t fId = kInvalidDescriptorId;
      const RColumn *fColumn = nullptr;

      /// Returns true for a valid column handle; fColumn and fId should always either both
      /// be valid or both be invalid.
      operator bool() const { return fId != kInvalidDescriptorId && fColumn; }
   };
   /// The column handle identifies a column with the current open page storage
   using ColumnHandle_t = RColumnHandle;

   /// Register a new column.  When reading, the column must exist in the ntuple on disk corresponding to the meta-data.
   /// When writing, every column can only be attached once.
   virtual ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) = 0;
   /// Unregisters a column.  A page source decreases the reference counter for the corresponding active column.
   /// For a page sink, dropping columns is currently a no-op.
   virtual void DropColumn(ColumnHandle_t columnHandle) = 0;

   /// Every page store needs to be able to free pages it handed out.  But Sinks and sources have different means
   /// of allocating pages.
   virtual void ReleasePage(RPage &page) = 0;

   /// Returns an empty metrics.  Page storage implementations usually have their own metrics.
   virtual RNTupleMetrics &GetMetrics();

   void SetTaskScheduler(RTaskScheduler *taskScheduler) { fTaskScheduler = taskScheduler; }
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageSink
\ingroup NTuple
\brief Abstract interface to write data into an ntuple

The page sink takes the list of columns and afterwards a series of page commits and cluster commits.
The user is responsible to commit clusters at a consistent point, i.e. when all pages corresponding to data
up to the given entry number are committed.
*/
// clang-format on
class RPageSink : public RPageStorage {
protected:
   RNTupleWriteOptions fOptions;

   /// Helper to zip pages and header/footer; comprises a 16MB unzip buffer.
   /// There could be concrete page sinks that don't need a compressor.  Therefore, and in order to stay consistent
   /// with the page source, we leave it up to the derived class whether or not the compressor gets constructed.
   std::unique_ptr<RNTupleCompressor> fCompressor;

   /// Building the ntuple descriptor while writing is done in the same way for all the storage sink implementations.
   /// Field, column, cluster ids and page indexes per cluster are issued sequentially starting with 0
   DescriptorId_t fLastFieldId = 0;
   DescriptorId_t fLastColumnId = 0;
   DescriptorId_t fLastClusterId = 0;
   NTupleSize_t fPrevClusterNEntries = 0;
   /// Keeps track of the number of elements in the currently open cluster. Indexed by column id.
   std::vector<RClusterDescriptor::RColumnRange> fOpenColumnRanges;
   /// Keeps track of the written pages in the currently open cluster. Indexed by column id.
   std::vector<RClusterDescriptor::RPageRange> fOpenPageRanges;
   RNTupleDescriptorBuilder fDescriptorBuilder;

   virtual void CreateImpl(const RNTupleModel &model) = 0;
   virtual RClusterDescriptor::RLocator CommitPageImpl(ColumnHandle_t columnHandle, const RPage &page) = 0;
   virtual RClusterDescriptor::RLocator CommitClusterImpl(NTupleSize_t nEntries) = 0;
   virtual void CommitDatasetImpl() = 0;

   /// Helper for streaming a page. This is commonly used in derived, concrete page sinks. Note that if
   /// compressionSetting is 0 (uncompressed) and the page is mappable, the returned sealed page will
   /// point directly to the input page buffer.  Otherwise, the sealed page references an internal buffer
   /// of fCompressor.  Thus, the buffer pointed to by the RSealedPage should never be freed.
   /// Usage of this method requires construction of fCompressor.
   RSealedPage SealPage(const RPage &page, const RColumnElementBase &element, int compressionSetting);

public:
   RPageSink(std::string_view ntupleName, const RNTupleWriteOptions &options);

   RPageSink(const RPageSink&) = delete;
   RPageSink& operator=(const RPageSink&) = delete;
   RPageSink(RPageSink&&) = default;
   RPageSink& operator=(RPageSink&&) = default;
   virtual ~RPageSink();

   /// Guess the concrete derived page source from the file name (location)
   static std::unique_ptr<RPageSink> Create(std::string_view ntupleName, std::string_view location,
                                            const RNTupleWriteOptions &options = RNTupleWriteOptions());
   EPageStorageType GetType() final { return EPageStorageType::kSink; }

   ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) final;
   void DropColumn(ColumnHandle_t /*columnHandle*/) final {}

   /// Physically creates the storage container to hold the ntuple (e.g., a keys a TFile or an S3 bucket)
   /// To do so, Create() calls CreateImpl() after updating the descriptor.
   /// Create() associates column handles to the columns referenced by the model
   void Create(RNTupleModel &model);
   /// Write a page to the storage. The column must have been added before.
   void CommitPage(ColumnHandle_t columnHandle, const RPage &page);
   /// Finalize the current cluster and create a new one for the following data.
   void CommitCluster(NTupleSize_t nEntries);
   /// Finalize the current cluster and the entrire data set.
   void CommitDataset() { CommitDatasetImpl(); }

   /// Get a new, empty page for the given column that can be filled with up to nElements.  If nElements is zero,
   /// the page sink picks an appropriate size.
   virtual RPage ReservePage(ColumnHandle_t columnHandle, std::size_t nElements = 0) = 0;
};

// clang-format off
/**
\class ROOT::Experimental::Detail::RPageSource
\ingroup NTuple
\brief Abstract interface to read data from an ntuple

The page source is initialized with the columns of interest. Pages from those columns can then be
mapped into memory. The page source also gives access to the ntuple's meta-data.
*/
// clang-format on
class RPageSource : public RPageStorage {
public:
   /// Derived from the model (fields) that are actually being requested at a given point in time
   using ColumnSet_t = std::unordered_set<DescriptorId_t>;

protected:
   RNTupleReadOptions fOptions;
   RNTupleDescriptor fDescriptor;
   /// The active columns are implicitly defined by the model fields or views
   ColumnSet_t fActiveColumns;

   /// Helper to unzip pages and header/footer; comprises a 16MB unzip buffer.
   /// Not all page sources need a decompressor (e.g. virtual ones for chains and friends don't), thus we
   /// leave it up to the derived class whether or not the decompressor gets constructed.
   std::unique_ptr<RNTupleDecompressor> fDecompressor;

   virtual RNTupleDescriptor AttachImpl() = 0;
   // Only called if a task scheduler is set. No-op be default.
   virtual void UnzipClusterImpl(RCluster * /* cluster */)
      { }

   /// Helper for unstreaming a page. This is commonly used in derived, concrete page sources.  The implementation
   /// currently always makes a memory copy, even if the sealed page is uncompressed and in the final memory layout.
   /// The optimization of directly mapping pages is left to the concrete page source implementations.
   /// Usage of this method requires construction of fDecompressor.
   unsigned char *UnsealPage(const RSealedPage &sealedPage, const RColumnElementBase &element);

public:
   RPageSource(std::string_view ntupleName, const RNTupleReadOptions &fOptions);
   RPageSource(const RPageSource&) = delete;
   RPageSource& operator=(const RPageSource&) = delete;
   RPageSource(RPageSource&&) = default;
   RPageSource& operator=(RPageSource&&) = default;
   virtual ~RPageSource();
   /// Guess the concrete derived page source from the file name (location)
   static std::unique_ptr<RPageSource> Create(std::string_view ntupleName, std::string_view location,
                                              const RNTupleReadOptions &options = RNTupleReadOptions());
   /// Open the same storage multiple time, e.g. for reading in multiple threads
   virtual std::unique_ptr<RPageSource> Clone() const = 0;

   EPageStorageType GetType() final { return EPageStorageType::kSource; }
   const RNTupleDescriptor &GetDescriptor() const { return fDescriptor; }
   ColumnHandle_t AddColumn(DescriptorId_t fieldId, const RColumn &column) final;
   void DropColumn(ColumnHandle_t columnHandle) final;

   /// Open the physical storage container for the tree
   void Attach() { fDescriptor = AttachImpl(); }
   NTupleSize_t GetNEntries();
   NTupleSize_t GetNElements(ColumnHandle_t columnHandle);
   ColumnId_t GetColumnId(ColumnHandle_t columnHandle);

   /// Allocates and fills a page that contains the index-th element
   virtual RPage PopulatePage(ColumnHandle_t columnHandle, NTupleSize_t globalIndex) = 0;
   /// Another version of PopulatePage that allows to specify cluster-relative indexes
   virtual RPage PopulatePage(ColumnHandle_t columnHandle, const RClusterIndex &clusterIndex) = 0;

   /// Populates all the pages of the given cluster id and columns; it is possible that some columns do not
   /// contain any pages.  The pages source may load more columns than the minimal necessary set from `columns`.
   /// To indicate which columns have been loaded, LoadCluster() must mark them with SetColumnAvailable().
   /// That includes the ones from the `columns` that don't have pages; otherwise subsequent requests
   /// for the cluster would assume an incomplete cluster and trigger loading again.
   /// LoadCluster() is typically called from the I/O thread of a cluster pool, i.e. the method runs
   /// concurrently to other methods of the page source.
   virtual std::unique_ptr<RCluster> LoadCluster(DescriptorId_t clusterId, const ColumnSet_t &columns) = 0;

   /// Parallel decompression and unpacking of the pages in the given cluster. The unzipped pages are supposed
   /// to be preloaded in a page pool attached to the source. The method is triggered by the cluster pool's
   /// unzip thread. It is an optional optimization, the method can safely do nothing. In particular, the
   /// actual implementation will only run if a task scheduler is set. In practice, a task scheduler is set
   /// if implicit multi-threading is turned on.
   void UnzipCluster(RCluster *cluster);
};

} // namespace Detail

} // namespace Experimental
} // namespace ROOT

#endif
<|endoftext|>


<commit_before>#include <boost/test/unit_test.hpp>

#include "../unittest_config.h"

#include "test_element.h"
#include "clotho/powerset/variable_subset.hpp"
#include "clotho/powerset/variable_subset_recombination.hpp"

typedef unsigned long Block;

typedef clotho::powersets::block_map< test_element, Block, clotho::powersets::normalized_key< test_element > > bmap;
typedef clotho::powersets::variable_subset< test_element, Block, bmap > subset_type;
typedef typename subset_type::powerset_type powerset_type;

/**
 * Simple classifier which just classifies all odd indices as being (1 - base) and (0 - alt)
 *
 * The idea being the recombination results as all odd elements from base, and all even
 * elements from alt
 */
template < class Block >
struct odd_from_base_classifier {
    typedef Block block_type;

    odd_from_base_classifier() : m_res(0) {}

/**
 * Does nothing
 */
    inline void updateOffset( unsigned int idx ) { return; }

/**
 * Classifies all indices which are odd as being (1 - base)
 */
    void operator()( unsigned int idx ) {
        if( idx & 1 ) {
            m_res |= ((block_type)1 << (idx % (sizeof(block_type) * 8)));
        }
    }

/**
 * Resets the result to be all 0s
 */
    void resetResult() {
        m_res = (block_type)0;
    }

/**
 * Returns the most recent classified indices
 */
    block_type getResult() const {
        return m_res;
    }

    block_type m_res;
};

typedef odd_from_base_classifier< Block > classifier_type;
typedef clotho::recombine::recombination< subset_type, classifier_type > recombination_type;

/**
 * A range based classifier.
 *
 * Assumes that recombination_points represent the upper bounds of each key range.
 * For example, the ranges of {0.25, 0.5, 0.75} would then be
 * (-inf, 0.25) -> base
 * [0.25, 0.5) -> alt
 * [0.5, 0.75) -> base
 * [0.75, inf) -> alt
 *
 * Classifier also takes an iterator to a list of elements.  Input index values are
 * relative offsets to the current list element.
 *
 * The result is a bit mask which classifies the list elements at relative indices
 * as being either in a "base" region (1), or "alt" region (0).
 */
template < class Block >
struct range_classifier {
    typedef Block block_type;
    typedef typename powerset_type::element_keyer_type keyer_type;
    typedef std::vector< keyer_type::key_type > recombination_points;
    typedef recombination_points::iterator      rec_iterator;

    range_classifier( const recombination_points & pts, typename powerset_type::cvariable_iterator vit) :
        m_res(0), m_pts(pts), m_vit(vit) {
        std::sort( m_pts.begin(), m_pts.end() );
    }

/**
 * Classifies a element at (m_vit + idx) as being either (1 - base) or not (0 - alt)
 *
 * Sets corresponding bit at idx of result accordingly
 */
    void operator()( unsigned int idx ) {
        keyer_type::key_type k = m_keyer( *(m_vit + idx) );

        rec_iterator rit = std::upper_bound( m_pts.begin(), m_pts.end(), k );

        if( (rit - m_pts.begin()) % 2 == 0 ) {
            m_res |= ((block_type)1 << idx);
        }
    }

/**
 * Used to advance the m_vit iterator by offset elements
 */
    inline void updateOffset( unsigned int offset ) {
        m_vit += offset;
    }

/**
 * Resets the result classification to all 0
 */
    void resetResult() {
        m_res = (block_type)0;
    }

/**
 * Returns the result of the most recent classified indices since last reset
 */
    block_type getResult() const {
        return m_res;
    }

    block_type              m_res;
    recombination_points    m_pts;
    typename powerset_type::cvariable_iterator m_vit;
    keyer_type              m_keyer;
};

typedef range_classifier< Block > classifier2_type;
typedef clotho::recombine::recombination< subset_type, classifier2_type > recombination2_type;

BOOST_AUTO_TEST_SUITE( test_recombination )

/**
 * Testing odd_from_base_classifier recombination with empty sequences (NULL shared_ptr)
 *
 * Case 1 - recombines empty sequences,
 * Case 2 & 3 - recombine empty sequence with sequence with single element (and vice versa).
 * Case 4 - recombine same sequence with element
 */
BOOST_AUTO_TEST_CASE( recombine_empty ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.empty_set();
    typename powerset_type::subset_ptr p1 = ps.empty_set();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();
    //

    recombination_type rec;
    classifier_type cfier;

    rec( p0, p1, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with p0 and p1 being empty, recombination would be empty");
    BOOST_REQUIRE_MESSAGE( rec.isMatchBase(), "Expected that with p0 and p1 being empty, recombination should match base");
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that with p0 and p1 being empty, recombination should match alt");

    test_element te( 0.5, 1.0 );
    c1_exp->addElement( te );

    rec( p0, c1_exp, cfier );

    BOOST_REQUIRE_MESSAGE( !rec.isEmpty(), "Nothing recombined. Expected that with base is empty, even element from c1_exp." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that with base is empty, even element from c1_exp." );

    rec( c1_exp, p0, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with base having only even element, and alt being empty, recombination should empty but is not." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Since alt is empty and recombination results in empty, rec should also match alt. However, this is not the case" );

    rec( c1_exp, c1_exp, cfier );
    BOOST_REQUIRE_MESSAGE( !rec.isEmpty(), "Expected that recombination of c1_exp with itself should not be empty." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchBase(), "Expected that recombination of c1_exp with itself should match base");
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that recombination  of c1_exp with itself should match alt");

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp , "Expected that c == c1_exp");
}

/**
 * Simple classifier test
 *
 * Create an empty powerset
 *
 * Create three empty subsets: P0, P1, C_exp;
 *
 * Alternate adding elements to P0 and P1.  Elements keys are normalized between [0, 1.0).
 *
 * C_exp is the subset with all elements.
 *
 * Recombine the sequences with P0 as the base sequence.  As a result of construction, P0 has
 * no odd index elements.  Therefore, any child sequence will inherit all 0s from P0.  Similarly,
 * P1 has no even elements.  Therefore, any child sequence will inherit all 0s from P1.  Hence,
 * this recombination results in the empty subset.
 *
 * Recombining the sequences with P1 as the base sequence results in the set with all elements
 * (C_exp).
 */
BOOST_AUTO_TEST_CASE( recombine_simple ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.create_subset();
    typename powerset_type::subset_ptr p1 = ps.create_subset();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();

    const unsigned int sub_div = 3;
    const double div_offset = (1.0/ ((double)sub_div*bmap::bits_per_block));

    for( unsigned int i = 0; i < sub_div * bmap::bits_per_block; ++i ) {
        double v = (double) (i % bmap::bits_per_block);

        unsigned int s = (i / bmap::bits_per_block );

        double k = v / (double) bmap::bits_per_block + (double)s*div_offset;
        test_element te(k, 1.0);
        if( i & 1 ) {
            p1->addElement( te );
        } else {
            p0->addElement( te );
        }
        c1_exp->addElement(te);

        typename powerset_type::element_index_type idx = ps.find(te);

        BOOST_REQUIRE_MESSAGE( idx == i, "Unexpected index " << idx << " returned for " << i << "(" << k << ")" );
    }

    recombination_type rec;
    classifier_type cfier;

    rec( p0, p1, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with p0 as base the simple recombination would be empty");

    rec( p1, p0, cfier );

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp , "Expected that with p1 as base the simple recombination would be all set");
}

/**
 * Range based recombination
 *
 * Create an empty powerset
 *
 * Create three empty subsets: P0, P1, C_exp;
 *
 * Define the upper bounds of recombination regions.
 *
 * Alternate adding elements to P0 and P1.  Elements keys are normalized between [0, 1.0).
 * Add elements to C_exp according to whether the parent subset it was added to, and
 * the value of its key.
 *
 * (-inf, 0.25) -> base
 * [0.25, 0.5) -> alt
 * [0.5, 0.75) -> base
 * [0.75, inf) -> alt
 *
 *
 */
BOOST_AUTO_TEST_CASE( recombine_range ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.create_subset();
    typename powerset_type::subset_ptr p1 = ps.create_subset();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();
    typename powerset_type::subset_ptr c0_exp = ps.create_subset();

    // after recombination
    // [0, 0.25) -> p0; [0.25, 0.5) -> p1; [0.5, 0.75) -> p0; [0.75, 1.0) -> p1
    classifier2_type::recombination_points pts(3);

    pts.push_back(0.25);
    pts.push_back(0.5);
    pts.push_back(0.75);

    const unsigned int sub_div = 3;
    const double div_offset = (1.0/ ((double)sub_div*bmap::bits_per_block));

    for( unsigned int i = 0; i < sub_div * bmap::bits_per_block; ++i ) {
        double v = (double) (i % bmap::bits_per_block);

        unsigned int s = (i / bmap::bits_per_block );

        double k = v / (double) bmap::bits_per_block + (double)s*div_offset;
        test_element te(k, 1.0);
        if( i & 1 ) {
            p1->addElement( te );
            if( (0.25 <= k && k < 0.5 ) ||
                (0.75 <= k && k < 1.0 ) ) {
                c1_exp->addElement(te);
            } else {
                c0_exp->addElement(te);
            }
        } else {
            p0->addElement( te );
            if( (0. <= k && k < 0.25 ) ||
                (0.5 <= k && k < 0.75) ) {
                c1_exp->addElement(te);
            } else {
                c0_exp->addElement(te);
            }
        }

        typename powerset_type::element_index_type idx = ps.find(te);

        BOOST_REQUIRE_MESSAGE( idx == i, "Unexpected index " << idx << " returned for " << i << "(" << k << ")" );
    }

    recombination2_type rec;
    classifier2_type cfier( pts, ps.variable_begin() );

    rec( p1, p0, cfier );

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp, "Unexpected result: p1 + p0" );

    classifier2_type cfier2( pts, ps.variable_begin() );
    rec( p0, p1, cfier2 );
    typename powerset_type::subset_ptr c2 = ps.create_subset( *rec.getResultSequence() );

//    std::cerr << "P0:    " << *p0 << std::endl;
//    std::cerr << "P1:    " << *p1 << std::endl;
//    std::cerr << "C:     " << *c << std::endl;
//    std::cerr << "E(C1): " << *c1_exp << std::endl;
//    std::cerr << "C2:    " << *c2 << std::endl;
//    std::cerr << "E(C2): " << *c0_exp << std::endl;

    BOOST_REQUIRE_MESSAGE( *c2 == *c0_exp, "Unexpected result: p0 + p1" );
}
BOOST_AUTO_TEST_SUITE_END()
<commit_msg>Added block iterator header<commit_after>#include <boost/test/unit_test.hpp>

#include "../unittest_config.h"

#include "test_element.h"
#include "clotho/utility/bit_block_iterator.hpp"
#include "clotho/powerset/variable_subset.hpp"
#include "clotho/powerset/variable_subset_recombination.hpp"

typedef unsigned long Block;

typedef clotho::powersets::block_map< test_element, Block, clotho::powersets::normalized_key< test_element > > bmap;
typedef clotho::powersets::variable_subset< test_element, Block, bmap > subset_type;
typedef typename subset_type::powerset_type powerset_type;

/**
 * Simple classifier which just classifies all odd indices as being (1 - base) and (0 - alt)
 *
 * The idea being the recombination results as all odd elements from base, and all even
 * elements from alt
 */
template < class Block >
struct odd_from_base_classifier {
    typedef Block block_type;

    odd_from_base_classifier() : m_res(0) {}

/**
 * Does nothing
 */
    inline void updateOffset( unsigned int idx ) { return; }

/**
 * Classifies all indices which are odd as being (1 - base)
 */
    void operator()( unsigned int idx ) {
        if( idx & 1 ) {
            m_res |= ((block_type)1 << (idx % (sizeof(block_type) * 8)));
        }
    }

/**
 * Resets the result to be all 0s
 */
    void resetResult() {
        m_res = (block_type)0;
    }

/**
 * Returns the most recent classified indices
 */
    block_type getResult() const {
        return m_res;
    }

    block_type m_res;
};

typedef odd_from_base_classifier< Block > classifier_type;
typedef clotho::recombine::recombination< subset_type, classifier_type > recombination_type;

/**
 * A range based classifier.
 *
 * Assumes that recombination_points represent the upper bounds of each key range.
 * For example, the ranges of {0.25, 0.5, 0.75} would then be
 * (-inf, 0.25) -> base
 * [0.25, 0.5) -> alt
 * [0.5, 0.75) -> base
 * [0.75, inf) -> alt
 *
 * Classifier also takes an iterator to a list of elements.  Input index values are
 * relative offsets to the current list element.
 *
 * The result is a bit mask which classifies the list elements at relative indices
 * as being either in a "base" region (1), or "alt" region (0).
 */
template < class Block >
struct range_classifier {
    typedef Block block_type;
    typedef typename powerset_type::element_keyer_type keyer_type;
    typedef std::vector< keyer_type::key_type > recombination_points;
    typedef recombination_points::iterator      rec_iterator;

    range_classifier( const recombination_points & pts, typename powerset_type::cvariable_iterator vit) :
        m_res(0), m_pts(pts), m_vit(vit) {
        std::sort( m_pts.begin(), m_pts.end() );
    }

/**
 * Classifies a element at (m_vit + idx) as being either (1 - base) or not (0 - alt)
 *
 * Sets corresponding bit at idx of result accordingly
 */
    void operator()( unsigned int idx ) {
        keyer_type::key_type k = m_keyer( *(m_vit + idx) );

        rec_iterator rit = std::upper_bound( m_pts.begin(), m_pts.end(), k );

        if( (rit - m_pts.begin()) % 2 == 0 ) {
            m_res |= ((block_type)1 << idx);
        }
    }

/**
 * Used to advance the m_vit iterator by offset elements
 */
    inline void updateOffset( unsigned int offset ) {
        m_vit += offset;
    }

/**
 * Resets the result classification to all 0
 */
    void resetResult() {
        m_res = (block_type)0;
    }

/**
 * Returns the result of the most recent classified indices since last reset
 */
    block_type getResult() const {
        return m_res;
    }

    block_type              m_res;
    recombination_points    m_pts;
    typename powerset_type::cvariable_iterator m_vit;
    keyer_type              m_keyer;
};

typedef range_classifier< Block > classifier2_type;
typedef clotho::recombine::recombination< subset_type, classifier2_type > recombination2_type;

BOOST_AUTO_TEST_SUITE( test_recombination )

/**
 * Testing odd_from_base_classifier recombination with empty sequences (NULL shared_ptr)
 *
 * Case 1 - recombines empty sequences,
 * Case 2 & 3 - recombine empty sequence with sequence with single element (and vice versa).
 * Case 4 - recombine same sequence with element
 */
BOOST_AUTO_TEST_CASE( recombine_empty ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.empty_set();
    typename powerset_type::subset_ptr p1 = ps.empty_set();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();
    //

    recombination_type rec;
    classifier_type cfier;

    rec( p0, p1, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with p0 and p1 being empty, recombination would be empty");
    BOOST_REQUIRE_MESSAGE( rec.isMatchBase(), "Expected that with p0 and p1 being empty, recombination should match base");
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that with p0 and p1 being empty, recombination should match alt");

    test_element te( 0.5, 1.0 );
    c1_exp->addElement( te );

    rec( p0, c1_exp, cfier );

    BOOST_REQUIRE_MESSAGE( !rec.isEmpty(), "Nothing recombined. Expected that with base is empty, even element from c1_exp." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that with base is empty, even element from c1_exp." );

    rec( c1_exp, p0, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with base having only even element, and alt being empty, recombination should empty but is not." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Since alt is empty and recombination results in empty, rec should also match alt. However, this is not the case" );

    rec( c1_exp, c1_exp, cfier );
    BOOST_REQUIRE_MESSAGE( !rec.isEmpty(), "Expected that recombination of c1_exp with itself should not be empty." );
    BOOST_REQUIRE_MESSAGE( rec.isMatchBase(), "Expected that recombination of c1_exp with itself should match base");
    BOOST_REQUIRE_MESSAGE( rec.isMatchAlt(), "Expected that recombination  of c1_exp with itself should match alt");

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp , "Expected that c == c1_exp");
}

/**
 * Simple classifier test
 *
 * Create an empty powerset
 *
 * Create three empty subsets: P0, P1, C_exp;
 *
 * Alternate adding elements to P0 and P1.  Elements keys are normalized between [0, 1.0).
 *
 * C_exp is the subset with all elements.
 *
 * Recombine the sequences with P0 as the base sequence.  As a result of construction, P0 has
 * no odd index elements.  Therefore, any child sequence will inherit all 0s from P0.  Similarly,
 * P1 has no even elements.  Therefore, any child sequence will inherit all 0s from P1.  Hence,
 * this recombination results in the empty subset.
 *
 * Recombining the sequences with P1 as the base sequence results in the set with all elements
 * (C_exp).
 */
BOOST_AUTO_TEST_CASE( recombine_simple ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.create_subset();
    typename powerset_type::subset_ptr p1 = ps.create_subset();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();

    const unsigned int sub_div = 3;
    const double div_offset = (1.0/ ((double)sub_div*bmap::bits_per_block));

    for( unsigned int i = 0; i < sub_div * bmap::bits_per_block; ++i ) {
        double v = (double) (i % bmap::bits_per_block);

        unsigned int s = (i / bmap::bits_per_block );

        double k = v / (double) bmap::bits_per_block + (double)s*div_offset;
        test_element te(k, 1.0);
        if( i & 1 ) {
            p1->addElement( te );
        } else {
            p0->addElement( te );
        }
        c1_exp->addElement(te);

        typename powerset_type::element_index_type idx = ps.find(te);

        BOOST_REQUIRE_MESSAGE( idx == i, "Unexpected index " << idx << " returned for " << i << "(" << k << ")" );
    }

    recombination_type rec;
    classifier_type cfier;

    rec( p0, p1, cfier );

    BOOST_REQUIRE_MESSAGE( rec.isEmpty(), "Expected that with p0 as base the simple recombination would be empty");

    rec( p1, p0, cfier );

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp , "Expected that with p1 as base the simple recombination would be all set");
}

/**
 * Range based recombination
 *
 * Create an empty powerset
 *
 * Create three empty subsets: P0, P1, C_exp;
 *
 * Define the upper bounds of recombination regions.
 *
 * Alternate adding elements to P0 and P1.  Elements keys are normalized between [0, 1.0).
 * Add elements to C_exp according to whether the parent subset it was added to, and
 * the value of its key.
 *
 * (-inf, 0.25) -> base
 * [0.25, 0.5) -> alt
 * [0.5, 0.75) -> base
 * [0.75, inf) -> alt
 *
 *
 */
BOOST_AUTO_TEST_CASE( recombine_range ) {
    powerset_type ps;

    typename powerset_type::subset_ptr p0 = ps.create_subset();
    typename powerset_type::subset_ptr p1 = ps.create_subset();
    typename powerset_type::subset_ptr c1_exp = ps.create_subset();
    typename powerset_type::subset_ptr c0_exp = ps.create_subset();

    // after recombination
    // [0, 0.25) -> p0; [0.25, 0.5) -> p1; [0.5, 0.75) -> p0; [0.75, 1.0) -> p1
    classifier2_type::recombination_points pts(3);

    pts.push_back(0.25);
    pts.push_back(0.5);
    pts.push_back(0.75);

    const unsigned int sub_div = 3;
    const double div_offset = (1.0/ ((double)sub_div*bmap::bits_per_block));

    for( unsigned int i = 0; i < sub_div * bmap::bits_per_block; ++i ) {
        double v = (double) (i % bmap::bits_per_block);

        unsigned int s = (i / bmap::bits_per_block );

        double k = v / (double) bmap::bits_per_block + (double)s*div_offset;
        test_element te(k, 1.0);
        if( i & 1 ) {
            p1->addElement( te );
            if( (0.25 <= k && k < 0.5 ) ||
                (0.75 <= k && k < 1.0 ) ) {
                c1_exp->addElement(te);
            } else {
                c0_exp->addElement(te);
            }
        } else {
            p0->addElement( te );
            if( (0. <= k && k < 0.25 ) ||
                (0.5 <= k && k < 0.75) ) {
                c1_exp->addElement(te);
            } else {
                c0_exp->addElement(te);
            }
        }

        typename powerset_type::element_index_type idx = ps.find(te);

        BOOST_REQUIRE_MESSAGE( idx == i, "Unexpected index " << idx << " returned for " << i << "(" << k << ")" );
    }

    recombination2_type rec;
    classifier2_type cfier( pts, ps.variable_begin() );

    rec( p1, p0, cfier );

    typename powerset_type::subset_ptr c = ps.create_subset( *rec.getResultSequence() );

    BOOST_REQUIRE_MESSAGE( *c == *c1_exp, "Unexpected result: p1 + p0" );

    classifier2_type cfier2( pts, ps.variable_begin() );
    rec( p0, p1, cfier2 );
    typename powerset_type::subset_ptr c2 = ps.create_subset( *rec.getResultSequence() );

//    std::cerr << "P0:    " << *p0 << std::endl;
//    std::cerr << "P1:    " << *p1 << std::endl;
//    std::cerr << "C:     " << *c << std::endl;
//    std::cerr << "E(C1): " << *c1_exp << std::endl;
//    std::cerr << "C2:    " << *c2 << std::endl;
//    std::cerr << "E(C2): " << *c0_exp << std::endl;

    BOOST_REQUIRE_MESSAGE( *c2 == *c0_exp, "Unexpected result: p0 + p1" );
}
BOOST_AUTO_TEST_SUITE_END()
<|endoftext|>


<commit_before>#include "RenderWindow.h"
#include <assert.h>

RenderWindow::RenderWindow(QString dataset, QListWidget *listWidget_Lock, unsigned int iCounter, QWidget* parent /* = 0 */, Qt::WindowFlags flags /* = 0 */) :
	QDialog(parent, flags),
	m_strDataset(dataset),
	m_listWidget_Lock(listWidget_Lock)
{
	setupUi(this);

	m_strID = tr("[%1] %2").arg(iCounter).arg(m_strDataset);
	setWindowTitle(m_strID);
	m_listWidget_Lock->addItem(m_strID);

	LoadImages();

	ToggleRenderWindowView1x3();
	resize(405, 278);
}

RenderWindow::~RenderWindow() {

}

void RenderWindow::LoadImages() {
	m_Pixmap1x3.load("RenderWin1x3.png");
	m_Pixmap2x2.load("RenderWin2x2.png");
	m_PixmapSingle.load("RenderWin1.png");
}

void RenderWindow::ToggleRenderWindowView1x3() {
	label->setPixmap(m_Pixmap1x3);
}

void RenderWindow::ToggleRenderWindowView2x2() {
	label->setPixmap(m_Pixmap2x2);
}

void RenderWindow::ToggleRenderWindowViewSingle() {
	label->setPixmap(m_PixmapSingle);
}

void RenderWindow::closeEvent(QCloseEvent *event) {
	QDialog::closeEvent(event);

	QList<QListWidgetItem*> l = m_listWidget_Lock->findItems(m_strID,  Qt::MatchExactly);

	assert(l.size() == 1); // if l.size() != 1 something went wrong during the creation of the list

	delete l[0];
}
<commit_msg><commit_after>#include "RenderWindow.h"
#include <assert.h>

RenderWindow::RenderWindow(QString dataset, QListWidget *listWidget_Lock, unsigned int iCounter, QWidget* parent /* = 0 */, Qt::WindowFlags flags /* = 0 */) :
	QDialog(parent, flags),
	m_strDataset(dataset),
	m_listWidget_Lock(listWidget_Lock)
{
	setupUi(this);

	m_strID = tr("[%1] %2").arg(iCounter).arg(m_strDataset);
	setWindowTitle(m_strID);
	m_listWidget_Lock->addItem(m_strID);

	LoadImages();

	ToggleRenderWindowView1x3();
}

RenderWindow::~RenderWindow() {

}

void RenderWindow::LoadImages() {
	m_Pixmap1x3.load("RenderWin1x3.png");
	m_Pixmap2x2.load("RenderWin2x2.png");
	m_PixmapSingle.load("RenderWin1.png");
}

void RenderWindow::ToggleRenderWindowView1x3() {
	label->setPixmap(m_Pixmap1x3);
}

void RenderWindow::ToggleRenderWindowView2x2() {
	label->setPixmap(m_Pixmap2x2);
}

void RenderWindow::ToggleRenderWindowViewSingle() {
	label->setPixmap(m_PixmapSingle);
}

void RenderWindow::closeEvent(QCloseEvent *event) {
	QDialog::closeEvent(event);

	QList<QListWidgetItem*> l = m_listWidget_Lock->findItems(m_strID,  Qt::MatchExactly);

	assert(l.size() == 1); // if l.size() != 1 something went wrong during the creation of the list

	delete l[0];
}
<|endoftext|>