When my Floating-Point Guide was yesterday published on slashdot, I got a lot of flak for my suggested comparison function, which was indeed inadequate. So I finally did the sensible thing and wrote a test suite to see whether I could get them all to pass. Here is my result so far. And I wonder if this is really as good as one can get with a generic (i.e. not application specific) float comparison function, or whether I still missed some edge cases.
(Code updated to fix error)
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
/**
* Test suite to demonstrate a good method for comparing floating-point values using an epsilon. Run via JUnit 4.
*
* Note: this function attempts a "one size fits all" solution. There may be some edge cases for which it still
* produces unexpected results, and some of the tests it was developed to pass probably specify behaviour that is
* not appropriate for some applications. Before using it, make sure it's appropriate for your application!
*
* From http://floating-point-gui.de
*
* #author Michael Borgwardt
*/
public class NearlyEqualsTest {
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a * b == 0) { // a or b or both are zero
// relative error is not meaningful here
return diff < (epsilon * epsilon);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
public static boolean nearlyEqual(float a, float b) {
return nearlyEqual(a, b, 0.000001f);
}
/** Regular large numbers - generally not problematic */
#Test
public void big() {
assertTrue(nearlyEqual(1000000f, 1000001f));
assertTrue(nearlyEqual(1000001f, 1000000f));
assertFalse(nearlyEqual(10000f, 10001f));
assertFalse(nearlyEqual(10001f, 10000f));
}
/** Negative large numbers */
#Test
public void bigNeg() {
assertTrue(nearlyEqual(-1000000f, -1000001f));
assertTrue(nearlyEqual(-1000001f, -1000000f));
assertFalse(nearlyEqual(-10000f, -10001f));
assertFalse(nearlyEqual(-10001f, -10000f));
}
/** Numbers around 1 */
#Test
public void mid() {
assertTrue(nearlyEqual(1.0000001f, 1.0000002f));
assertTrue(nearlyEqual(1.0000002f, 1.0000001f));
assertFalse(nearlyEqual(1.0002f, 1.0001f));
assertFalse(nearlyEqual(1.0001f, 1.0002f));
}
/** Numbers around -1 */
#Test
public void midNeg() {
assertTrue(nearlyEqual(-1.000001f, -1.000002f));
assertTrue(nearlyEqual(-1.000002f, -1.000001f));
assertFalse(nearlyEqual(-1.0001f, -1.0002f));
assertFalse(nearlyEqual(-1.0002f, -1.0001f));
}
/** Numbers between 1 and 0 */
#Test
public void small() {
assertTrue(nearlyEqual(0.000000001000001f, 0.000000001000002f));
assertTrue(nearlyEqual(0.000000001000002f, 0.000000001000001f));
assertFalse(nearlyEqual(0.000000000001002f, 0.000000000001001f));
assertFalse(nearlyEqual(0.000000000001001f, 0.000000000001002f));
}
/** Numbers between -1 and 0 */
#Test
public void smallNeg() {
assertTrue(nearlyEqual(-0.000000001000001f, -0.000000001000002f));
assertTrue(nearlyEqual(-0.000000001000002f, -0.000000001000001f));
assertFalse(nearlyEqual(-0.000000000001002f, -0.000000000001001f));
assertFalse(nearlyEqual(-0.000000000001001f, -0.000000000001002f));
}
/** Comparisons involving zero */
#Test
public void zero() {
assertTrue(nearlyEqual(0.0f, 0.0f));
assertTrue(nearlyEqual(0.0f, -0.0f));
assertTrue(nearlyEqual(-0.0f, -0.0f));
assertFalse(nearlyEqual(0.00000001f, 0.0f));
assertFalse(nearlyEqual(0.0f, 0.00000001f));
assertFalse(nearlyEqual(-0.00000001f, 0.0f));
assertFalse(nearlyEqual(0.0f, -0.00000001f));
assertTrue(nearlyEqual(0.0f, 0.00000001f, 0.01f));
assertTrue(nearlyEqual(0.00000001f, 0.0f, 0.01f));
assertFalse(nearlyEqual(0.00000001f, 0.0f, 0.000001f));
assertFalse(nearlyEqual(0.0f, 0.00000001f, 0.000001f));
assertTrue(nearlyEqual(0.0f, -0.00000001f, 0.1f));
assertTrue(nearlyEqual(-0.00000001f, 0.0f, 0.1f));
assertFalse(nearlyEqual(-0.00000001f, 0.0f, 0.00000001f));
assertFalse(nearlyEqual(0.0f, -0.00000001f, 0.00000001f));
}
/** Comparisons of numbers on opposite sides of 0 */
#Test
public void opposite() {
assertFalse(nearlyEqual(1.000000001f, -1.0f));
assertFalse(nearlyEqual(-1.0f, 1.000000001f));
assertFalse(nearlyEqual(-1.000000001f, 1.0f));
assertFalse(nearlyEqual(1.0f, -1.000000001f));
assertTrue(nearlyEqual(1e10f * Float.MIN_VALUE, -1e10f * Float.MIN_VALUE));
}
/**
* The really tricky part - comparisons of numbers very close to zero.
*/
#Test
public void ulp() {
assertTrue(nearlyEqual(Float.MIN_VALUE, -Float.MIN_VALUE));
assertTrue(nearlyEqual(-Float.MIN_VALUE, Float.MIN_VALUE));
assertTrue(nearlyEqual(Float.MIN_VALUE, 0));
assertTrue(nearlyEqual(0, Float.MIN_VALUE));
assertTrue(nearlyEqual(-Float.MIN_VALUE, 0));
assertTrue(nearlyEqual(0, -Float.MIN_VALUE));
assertFalse(nearlyEqual(0.000000001f, -Float.MIN_VALUE));
assertFalse(nearlyEqual(0.000000001f, Float.MIN_VALUE));
assertFalse(nearlyEqual(Float.MIN_VALUE, 0.000000001f));
assertFalse(nearlyEqual(-Float.MIN_VALUE, 0.000000001f));
assertFalse(nearlyEqual(1e25f * Float.MIN_VALUE, 0.0f, 1e-12f));
assertFalse(nearlyEqual(0.0f, 1e25f * Float.MIN_VALUE, 1e-12f));
assertFalse(nearlyEqual(1e25f * Float.MIN_VALUE, -1e25f * Float.MIN_VALUE, 1e-12f));
assertTrue(nearlyEqual(1e25f * Float.MIN_VALUE, 0.0f, 1e-5f));
assertTrue(nearlyEqual(0.0f, 1e25f * Float.MIN_VALUE, 1e-5f));
assertTrue(nearlyEqual(1e20f * Float.MIN_VALUE, -1e20f * Float.MIN_VALUE, 1e-5f));
}
}
The main problem I see is you don't allow the user to control epsilon.
Also epsilon changes depending on the order of magnitude of the numbers being compared. Near zero epsilon is small, near the maximum power epsilon is large.
I think whenever you need to talk about such concepts as "close enough" it becomes an application level design decision. You can't write a generic library for that.
After sleeping over it, I've realized that this part was rubbish:
if (a*b==0) {
return diff < Float.MIN_VALUE / epsilon;
This becomes less strict as epsilon gets smaller! A more sensible version:
if (a * b == 0) {
return diff < (epsilon * epsilon);
Still, the two branches of the if are not very consistent with each other. It's much stricter when a or b is very small than when one of them is zero. I'm really starting to think that using integer comparison is an overall better method.
Related
According to https://docs.opencv.org/3.4.0/d1/dd7/structcv_1_1line__descriptor_1_1KeyLine.html it should be defined in line_descriptor.cpp and line_descriptor.hpp, but I can't seem to find it there. Anyone know where the declaration and definition are?
Both definition and declaration of KeyLine are in descriptor.hpp at line 102, see by yourself
struct CV_EXPORTS KeyLine
{
public:
/** orientation of the line */
float angle;
/** object ID, that can be used to cluster keylines by the line they represent */
int class_id;
/** octave (pyramid layer), from which the keyline has been extracted */
int octave;
/** coordinates of the middlepoint */
Point2f pt;
/** the response, by which the strongest keylines have been selected.
It's represented by the ratio between line's length and maximum between
image's width and height */
float response;
/** minimum area containing line */
float size;
/** lines's extremes in original image */
float startPointX;
float startPointY;
float endPointX;
float endPointY;
/** line's extremes in image it was extracted from */
float sPointInOctaveX;
float sPointInOctaveY;
float ePointInOctaveX;
float ePointInOctaveY;
/** the length of line */
float lineLength;
/** number of pixels covered by the line */
int numOfPixels;
/** Returns the start point of the line in the original image */
Point2f getStartPoint() const
{
return Point2f(startPointX, startPointY);
}
/** Returns the end point of the line in the original image */
Point2f getEndPoint() const
{
return Point2f(endPointX, endPointY);
}
/** Returns the start point of the line in the octave it was extracted from */
Point2f getStartPointInOctave() const
{
return Point2f(sPointInOctaveX, sPointInOctaveY);
}
/** Returns the end point of the line in the octave it was extracted from */
Point2f getEndPointInOctave() const
{
return Point2f(ePointInOctaveX, ePointInOctaveY);
}
/** constructor */
KeyLine()
{
}
};
Also it is unclear for me why did you think it should be in line_descriptor.cpp or line_descriptor.hpp
I want to change the loss calculation method in loss layer when the iteration times reach a certain number.
In order to realize it I think I need to get the current learning rate or iteration times, then I use if phrase to choose changing loss calculation method or not.
You can add a member variable in Caffe class to save the current learning rate or iteration times and access it in the layer where you want.
For example, to get the current iteration times where you want you need to make 3 key modifications(for simplification):
In common.hpp:
class Caffe {
public:
static Caffe& Get();
...//Some other public members
//Returns the current iteration times
inline static int current_iter() { return Get().cur_iter_; }
//Sets the current iteration times
inline static void set_cur_iter(int iter) { Get().cur_iter_ = iter; }
protected:
//The variable to save the current itertion times
int cur_iter_;
...//Some other protected members
}
In solver.cpp:
template <typename Dtype>
void Solver<Dtype>::Step(int iters) {
...
while (iter_ < stop_iter) {
Caffe::set_cur_iter(iter_ );
...//Left Operations
}
}
The place where you want to access the current iteration times:
template <typename Dtype>
void SomeLayer<Dtype>::some_func() {
int current_iter = Caffe::current_iter();
...//Operations you want
}
AFAIK there is no direct access from within a python layer to the solver's iteration count and the learning rate.
However, you can keep a counter of your own
import caffe
class IterCounterLossLayer(caffe.Layer):
def setup(self, bottom, top):
# do your setup here...
self.iter_counter = 0 # setup a counter
def reshape(self, bottom, top):
# reshape code here...
# loss output is scalar
top[0].reshape(1)
def forward(self, bottom, top):
if self.iter_counter < 1000:
# some way of computing the loss
# ...
else:
# another way
# ...
self.iter_counter += 1 # increment, you may consider incrementing by bottom[0].shape[0] the batch size...
def backward(self, top, propagate_down, bottom):
if self.iter_counter < 1000:
# gradients need to fit the loss
# ...
else:
# another way
# ...
To get iteration, you can use my count_layer as a bottom layer of your custom layer, with which you can benefit from following aspects:
When you finetune with weights, the iteration number continues from the weights you save.
Have a modular implementation.
No need to change existing caffe codes.
train_val.prototxt
layer {
name: "iteration"
top: "iteration"
type: "Count"
}
count_layer.hpp
#ifndef CAFFE_COUNT_LAYER_HPP_
#define CAFFE_COUNT_LAYER_HPP_
#include <vector>
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
class CountLayer : public Layer<Dtype> {
public:
explicit CountLayer(const LayerParameter& param)
: Layer<Dtype>(param), delta_(1) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
if (this->blobs_.size() > 0) {
LOG(INFO) << "Skipping parameter initialization";
} else {
this->blobs_.resize(1);
this->blobs_[0].reset(new Blob<Dtype>());
if (this->layer_param_.count_param().has_shape()){
this->blobs_[0]->Reshape(this->layer_param_.count_param().shape());
} else{
this->blobs_[0]->Reshape(vector<int>{1, 1});
}
shared_ptr<Filler<Dtype> > base_filler(GetFiller<Dtype>(
this->layer_param_.count_param().base_filler()));
base_filler->Fill(this->blobs_[0].get());
}
top[0]->Reshape(this->blobs_[0]->shape());
string name = this->layer_param().name();
if (name == ""){
name = "Count";
}
if (this->layer_param_.param_size() <= 0){
LOG(INFO) << "Layer " << name << "'s decay_mult has been set to 0";
this->layer_param_.add_param()->set_decay_mult(Dtype(0));
} else if (!this->layer_param_.param(0).has_decay_mult()){
LOG(INFO) << "Layer " << name << "'s decay_mult has been set to 0";
this->layer_param_.mutable_param(0)->set_decay_mult(0);
}
delta_ = Dtype(this->layer_param_.count_param().delta());
//this make top start from base and make finetune correct
caffe_add_scalar(this->blobs_[0]->count(), -delta_, this->blobs_[0]->mutable_cpu_data());
}
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) { }
virtual inline const char* type() const { return "Parameter"; }
virtual inline int ExactNumBottomBlobs() const { return 0; }
virtual inline int ExactNumTopBlobs() const { return 1; }
protected:
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
caffe_add_scalar(this->blobs_[0]->count(), delta_, this->blobs_[0]->mutable_cpu_data());
top[0]->ShareData(*(this->blobs_[0]));
top[0]->ShareDiff(*(this->blobs_[0]));
}
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom)
{
}
private:
Dtype delta_;
};
} // namespace caffe
#endif
caffe_layer.cpp
#include "caffe/layers/count_layer.hpp"
namespace caffe {
INSTANTIATE_CLASS(CountLayer);
REGISTER_LAYER_CLASS(Count);
} // namespace caffe
caffe.proto
optional CountParameter count_param = 666;
...
message CountParameter {
optional BlobShape shape = 1;
optional FillerParameter base_filler = 2; // The filler for the base
optional float delta = 3 [default = 1];
}
I'm looking for the proper way to return a custom error from a JSON-RPC exposed class.
JSON-RPC has a special format for reporting error conditions. All errors need to provide, minimally, an error message and error code; optionally, they can provide additional data, such as a backtrace.
Error codes are derived from those recommended by the XML-RPC EPI project. Zend\Json\Server appropriately assigns the code based on the error condition. For application exceptions, the code ‘-32000’ is used.
I will use the divide method of the sample code from documentation to explain:
<?php
/**
* Calculator - sample class to expose via JSON-RPC
*/
class Calculator
{
/**
* Return sum of two variables
*
* #param int $x
* #param int $y
* #return int
*/
public function add($x, $y)
{
return $x + $y;
}
/**
* Return difference of two variables
*
* #param int $x
* #param int $y
* #return int
*/
public function subtract($x, $y)
{
return $x - $y;
}
/**
* Return product of two variables
*
* #param int $x
* #param int $y
* #return int
*/
public function multiply($x, $y)
{
return $x * $y;
}
/**
* Return the division of two variables
*
* #param int $x
* #param int $y
* #return float
*/
public function divide($x, $y)
{
if ($y == 0) {
// Say "y must not be zero" in proper JSON-RPC error format
// e.g. something like {"error":{"code":-32600,"message":"Invalid Request","data":null},"id":null}
} else {
return $x / $y;
}
}
}
$server = new Zend\Json\Server\Server();
$server->setClass('Calculator');
if ('GET' == $_SERVER['REQUEST_METHOD']) {
// Indicate the URL endpoint, and the JSON-RPC version used:
$server->setTarget('/json-rpc.php')
->setEnvelope(Zend\Json\Server\Smd::ENV_JSONRPC_2);
// Grab the SMD
$smd = $server->getServiceMap();
// Return the SMD to the client
header('Content-Type: application/json');
echo $smd;
return;
}
$server->handle();
p.s. Yes I tried Google search.
Disclaimer: I have no experience in using Zend\Json\Server whatsoever :)
If you talk about an error response, I can correlate that to the Server::fault() method (also available on Github). So I assume if fault() is called and injected into the respones, it would return the response with error messages according to your referred recommended XML-RPC server standard.
The handler method proxies the actual work to _handle() (linked to the source) where a try/catch is encapsuling the dispatching to the (in your case) Calculator class.
The fault is called based on the exception message and exception code. As such I think it's simply throwing an exception and setting the right message/code there:
use Zend\Json\Server\Error;
class Calculator
{
public function divide($x, $y)
{
if (0 === $y) {
throw new InvalidArgumentException(
'Denominator must be a non-zero numerical',
Error::ERROR_INVALID_PARAMS
);
}
// Rest here
}
// Rest here
}
PS. I also changed your error code here, as to me, it feels -32602 (invalid params) is more appropriate than -32600 (invalid request.
lets say I have an ActivePivot cube with facts containing just Value, and Currency.
lets say my cube has Currency as a regular dimension.
We fill the cube with facts that have many currencies.
We have a forex service that takes the currency and reference currency to get a rate.
Now, Value.SUM doesn't make any sense, we are adding up values with different currencies, so we want to have a post processor that can convert all values to a reference currency, say, USD, then sum them, so we write a post processor that extends ADynamicAggregationPostProcessor, specify Currency as a leaf level dimension, and use the forex service to do the conversion, and we are happy.
But, lets say we don't want to convert just to USD, we want to convert to 10 different currencies and see the results next to each other on the screen.
So we create an Analysis dimension, say ReferenceCurrency, with 10 members.
My question is: how can I alter the above post processor to handle the Analysis dimension? The plain vanilla ADynamicAggregationPostProcessor does not handle Analysis dimensions, only the default member is visible to this post processor. Other post processors that handle Analysis dimensions, like DefaultAggregatePostProcessor do not have a means for specifying leaf levels, so I cannot get the aggregates by Currency, and so cannot do the forex conversion. How can I have my cake and eat it too?
It looks like you want to use two advanced features of ActivePivot at the same time (Analysis dimensions to expose several outcomes of the same aggregate, and dynamic aggregation to aggregate amounts expressed in different currencies).
Separately each one is fairly easy to setup through configuration and a few lines of code to inject. But to interleave both you will need to understand the internals of post processor evaluation, and inject business logic at the right places.
Here is an example based on ActivePivot 4.3.3. It has been written in the open-source Sandbox Application so that you can run it quickly before adapting it to your own project.
First we need a simple analysis dimension to hold the possible reference currencies:
package com.quartetfs.pivot.sandbox.postprocessor.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import com.quartetfs.biz.pivot.cube.hierarchy.axis.impl.AAnalysisDimension;
import com.quartetfs.fwk.QuartetExtendedPluginValue;
/**
*
* An analysis dimension bearing the
* list of possible reference currencies.
*
* #author Quartet FS
*
*/
#QuartetExtendedPluginValue(interfaceName = "com.quartetfs.biz.pivot.cube.hierarchy.IDimension", key = ReferenceCurrencyDimension.TYPE)
public class ReferenceCurrencyDimension extends AAnalysisDimension {
/** serialVersionUID */
private static final long serialVersionUID = 42706811331081328L;
/** Default reference currency */
public static final String DEFAULT_CURRENCY = "EUR";
/** Static list of non-default possible reference currencies */
public static final List<Object[]> CURRENCIES;
static {
List<Object[]> currencies = new ArrayList<Object[]>();
currencies.add(new Object[] {"USD"});
currencies.add(new Object[] {"GBP"});
currencies.add(new Object[] {"JPY"});
CURRENCIES = Collections.unmodifiableList(currencies);
}
/** Plugin type */
public static final String TYPE = "REF_CCY";
/** Constructor */
public ReferenceCurrencyDimension(String name, int ordinal, Properties properties, Set<String> measureGroups) {
super(name, ordinal, properties, measureGroups);
}
#Override
public Object getDefaultDiscriminator(int levelOrdinal) { return DEFAULT_CURRENCY; }
#Override
public Collection<Object[]> buildDiscriminatorPaths() { return CURRENCIES; }
#Override
public int getLevelsCount() { return 1; }
#Override
public String getLevelName(int levelOrdinal) {
return levelOrdinal == 0 ? "Currency" : super.getLevelName(levelOrdinal);
}
#Override
public String getType() { return TYPE; }
}
Then the post processor itself, a customized dynamic aggregation post processor modified to handle the analysis dimension and output the same aggregate multiple times, one time per reference currency.
package com.quartetfs.pivot.sandbox.postprocessor.impl;
import java.util.List;
import java.util.Properties;
import com.quartetfs.biz.pivot.IActivePivot;
import com.quartetfs.biz.pivot.ILocation;
import com.quartetfs.biz.pivot.ILocationPattern;
import com.quartetfs.biz.pivot.aggfun.IAggregationFunction;
import com.quartetfs.biz.pivot.cellset.ICellSet;
import com.quartetfs.biz.pivot.cube.hierarchy.IDimension;
import com.quartetfs.biz.pivot.cube.hierarchy.axis.IAxisMember;
import com.quartetfs.biz.pivot.impl.Location;
import com.quartetfs.biz.pivot.postprocessing.impl.ADynamicAggregationPostProcessor;
import com.quartetfs.biz.pivot.postprocessing.impl.ADynamicAggregationProcedure;
import com.quartetfs.biz.pivot.query.IQueryCache;
import com.quartetfs.biz.pivot.query.aggregates.IAggregatesRetriever;
import com.quartetfs.biz.pivot.query.aggregates.RetrievalException;
import com.quartetfs.fwk.QuartetException;
import com.quartetfs.fwk.QuartetExtendedPluginValue;
import com.quartetfs.pivot.sandbox.service.impl.ForexService;
import com.quartetfs.tech.type.IDataType;
import com.quartetfs.tech.type.impl.DoubleDataType;
/**
* Forex post processor with two features:
* <ul>
* <li>Dynamically aggregates amounts in their native currencies into reference currency
* <li>Applies several reference currencies, exploded along an analysis dimension.
* </ul>
*
* #author Quartet FS
*/
#QuartetExtendedPluginValue(interfaceName = "com.quartetfs.biz.pivot.postprocessing.IPostProcessor", key = ForexPostProcessor.TYPE)
public class ForexPostProcessor extends ADynamicAggregationPostProcessor<Double> {
/** serialVersionUID */
private static final long serialVersionUID = 15874126988574L;
/** post processor plugin type */
public final static String TYPE = "FOREX";
/** Post processor return type */
private static final IDataType<Double> DATA_TYPE = new DoubleDataType();
/** Ordinal of the native currency dimension */
protected int nativeCurrencyDimensionOrdinal;
/** Ordinal of the native currency level */
protected int nativeCurrencyLevelOrdinal;
/** Ordinal of the reference currencies dimension */
protected int referenceCurrenciesOrdinal;
/** forex service*/
private ForexService forexService;
/** constructor */
public ForexPostProcessor(String name, IActivePivot pivot) {
super(name, pivot);
}
/** Don't forget to inject the Forex service into the post processor */
public void setForexService(ForexService forexService) {
this.forexService = forexService;
}
/** post processor initialization */
#Override
public void init(Properties properties) throws QuartetException {
super.init(properties);
nativeCurrencyDimensionOrdinal = leafLevelsOrdinals.get(0)[0];
nativeCurrencyLevelOrdinal = leafLevelsOrdinals.get(0)[1];
IDimension referenceCurrenciesDimension = getDimension("ReferenceCurrencies");
referenceCurrenciesOrdinal = referenceCurrenciesDimension.getOrdinal();
}
/**
* Handling of the analysis dimension:<br>
* Before retrieving leaves, wildcard the reference currencies dimension.
*/
protected ICellSet retrieveLeaves(ILocation location, IAggregatesRetriever retriever) throws RetrievalException {
ILocation baseLocation = location;
if(location.getLevelDepth(referenceCurrenciesOrdinal-1) > 0) {
Object[][] array = location.arrayCopy();
array[referenceCurrenciesOrdinal-1][0] = null; // wildcard
baseLocation = new Location(array);
}
return super.retrieveLeaves(baseLocation, retriever);
}
/**
* Perform the evaluation of the post processor on a leaf (as defined in the properties).
* Here the leaf level is the UnderlierCurrency level in the Underlyings dimension .
*/
#Override
protected Double doLeafEvaluation(ILocation leafLocation, Object[] underlyingMeasures) throws QuartetException {
// Extract the native and reference currencies from the evaluated location
String currency = (String) leafLocation.getCoordinate(nativeCurrencyDimensionOrdinal-1, nativeCurrencyLevelOrdinal);
String refCurrency = (String) leafLocation.getCoordinate(referenceCurrenciesOrdinal-1, 0);
// Retrieve the measure in the native currency
double nativeAmount = (Double) underlyingMeasures[0];
// If currency is reference currency or measureNative is equal to 0.0 no need to convert
if ((currency.equals(refCurrency)) || (nativeAmount == .0) ) return nativeAmount;
// Retrieve the rate and rely on the IQueryCache
// in order to retrieve the same rate for the same currency for our query
IQueryCache queryCache = pivot.getContext().get(IQueryCache.class);
Double rate = (Double) queryCache.get(currency + "_" + refCurrency);
if(rate == null) {
Double rateRetrieved = forexService.retrieveQuotation(currency, refCurrency);
Double rateCached = (Double) queryCache.putIfAbsent(currency + "_" + refCurrency, rateRetrieved);
rate = rateCached == null ? rateRetrieved : rateCached;
}
// Compute equivalent in reference currency
return rate == null ? nativeAmount : nativeAmount * rate;
}
#Override
protected IDataType<Double> getDataType() { return DATA_TYPE; }
/** #return the type of this post processor, within the post processor extended plugin. */
#Override
public String getType() { return TYPE; }
/**
* #return our own custom dynamic aggregation procedure,
* so that we can inject our business logic.
*/
protected DynamicAggregationProcedure createProcedure(ICellSet cellSet, IAggregationFunction aggregationFunction, ILocationPattern pattern) {
return new DynamicAggregationProcedure(cellSet, aggregationFunction, pattern);
}
/**
* Custom dynamic aggregation procedure.<br>
* When the procedure is executed over a leaf location,
* we produce several aggregates instead of only one:
* one aggregate for each of the visible reference currencies.
*/
protected class DynamicAggregationProcedure extends ADynamicAggregationProcedure<Double> {
protected DynamicAggregationProcedure(ICellSet cellSet, IAggregationFunction aggregationFunction, ILocationPattern pattern) {
super(ForexPostProcessor.this, aggregationFunction, cellSet, pattern);
}
/**
* Execute the procedure over one row of the leaf cell set.
* We compute one aggregate for each of the reference currencies.
*/
#Override
public boolean execute(ILocation location, int rowId, Object[] measures) {
if(location.getLevelDepth(referenceCurrenciesOrdinal-1) > 0) {
// Lookup the visible reference currencies
IDimension referenceCurrenciesDimension = pivot.getDimensions().get(referenceCurrenciesOrdinal);
List<IAxisMember> referenceCurrencies = (List<IAxisMember>) referenceCurrenciesDimension.retrieveMembers(0);
for(IAxisMember member : referenceCurrencies) {
Object[][] array = location.arrayCopy();
array[referenceCurrenciesOrdinal-1][0] = member.getDiscriminator();
ILocation loc = new Location(array);
super.execute(loc, rowId, measures);
}
return true;
} else {
return super.execute(location, rowId, measures);
}
}
#Override
protected Double doLeafEvaluation(ILocation location, Object[] measures) throws QuartetException {
return ForexPostProcessor.this.doLeafEvaluation(location, measures);
}
}
}
In the description of your cube, the analysis dimension and the post processor would be exposed like this:
...
<dimension name="ReferenceCurrencies" pluginKey="REF_CCY" />
...
<measure name="cross" isIntrospectionMeasure="false">
<postProcessor pluginKey="FOREX">
<properties>
<entry key="id" value="pv.SUM" />
<entry key="underlyingMeasures" value="pv.SUM" />
<entry key="leafLevels" value="UnderlierCurrency#Underlyings" />
</properties>
</postProcessor>
</measure>
...
Analysis Dimensions brings so much complexity they should be considered apart of the others features of your cube. One way to handle your issue is then:
Add a first measure expanding properly along the analysis dimension. In your case, it will simply copy the underlying measure along ReferenceCurrency, optionally doing the FX conversion. This measure may be used as underlying of several measures.
Add a second measure, based on usual dynamic aggregation. This second implementation is very simple as it does not know there is an analysis dimension.
I am trying to make something along the lines of this: http://instaprint.me/, however I don't have a dedicated Linux machine, ink-less paper and WiFi. I do, on the other hand, have a desktop connected to the internet and a color photo-printer.
So what I had in mind was this -
Set up an Instagram API App that gets all the information of the most recent photo
A PHP script and a server that can produce a static URL for the most recent photo with a header("Content-Type: image/jpeg"), so that every time I take a new photo, the contents of the image on that static page changes to be the most recent photo.
Other pages, like the one mentioned above, that change to reflect the new location and caption of each photo I take, hosted on a static URL.
Some basic knowledge of Processing.
So here's how for I've got so far - I can download the most recent photo ID, and if it has changed since the last time it checked (15 seconds ago), then it proceeds to download the most recent image, caption and location.
I can then arrange these on the canvas to look like an old-fashioned polaroid. Now the only thing I have to do is print the canvas, at 150dpi, on a piece of 6x4in photo paper.
Anyone got any idea how I could do this?
Focusing on the part about bringing up large images in Processing, I did a Google search and came up with this thread. Quoting their problem and results:
The goal is to print a 180 pdi image with a shape depending on the
original canvas. So can be diffrent but it will end up being a
1500x1000mm picture approximately. So a big image. I'm not trying to
display it just to dump it into a file.
Then I setup a 64bits JVM. The latest one from Oracle website. Then I
created different size of picture and push the memory up to 1024MB. 5000x7500 test OK and 6000x9000 test OK
I tried to setup the memory up to 1500MB, but the JVM was not able to start.
So I tried 1200MB 8000x12000 test OK
So it doesn't do the image-printing part but it brings up key information on getting large images in memory via a Java Virtual Machine (JVM).
Lastly, this thread describes a TileSaver class for viewing/printing a large number of pixels using OpenGL. I pasted a large code block below. (This may be unrelated to your question but makes the answer more complete for other uses.)
import processing.opengl.*;
import toxi.geom.*;
Tiler tiler;
void setup() {
size(640,480,OPENGL);
tiler=new Tiler((PGraphics3D)g,NUM_TILES);
}
void draw() {
// see thread
}
/**
* Implements a screen tile exporter with support for FOV,
* clip planes and flexible file formats.
*
* Re-engineered from an older version by Marius Watz:
* http://workshop.evolutionzone.com/unlekkerlib/
*
* #author toxi <info at postspectacular dot com>
*/
class Tiler {
protected PGraphics3D gfx;
protected PImage buffer;
protected Vec2D[] tileOffsets;
protected double top;
protected double bottom;
protected double left;
protected double right;
protected Vec2D tileSize;
protected int numTiles;
protected int tileID;
protected float subTileID;
protected boolean isTiling;
protected String fileName;
protected String format;
protected double cameraFOV;
protected double cameraFar;
protected double cameraNear;
public Tiler(PGraphics3D g, int n) {
gfx = g;
numTiles = n;
}
public void chooseTile(int id) {
Vec2D o = tileOffsets[id];
gfx.frustum(o.x, o.x + tileSize.x, o.y, o.y + tileSize.y,
(float) cameraNear, (float) cameraFar);
}
public void initTiles(float fov, float near, float far) {
tileOffsets = new Vec2D[numTiles * numTiles];
double aspect = (double) gfx.width / gfx.height;
cameraFOV = fov;
cameraNear = near;
cameraFar = far;
top = Math.tan(cameraFOV * 0.5) * cameraNear;
bottom = -top;
left = aspect * bottom;
right = aspect * top;
int idx = 0;
tileSize = new Vec2D((float) (2 * right / numTiles), (float) (2 * top / numTiles));
double y = top - tileSize.y;
while (idx < tileOffsets.length) {
double x = left;
for (int xi = 0; xi < numTiles; xi++) {
tileOffsets[idx++] = new Vec2D((float) x, (float) y);
x += tileSize.x;
}
y -= tileSize.y;
}
}
public void post() {
if (isTiling) {
subTileID += 0.5;
if (subTileID > 1) {
int x = tileID % numTiles;
int y = tileID / numTiles;
gfx.loadPixels();
buffer.set(x * gfx.width, y * gfx.height, gfx);
if (tileID == tileOffsets.length - 1) {
buffer.save(fileName + "_" + buffer.width + "x"
+ buffer.height + "." + format);
buffer = null;
}
subTileID = 0;
isTiling = (++tileID < tileOffsets.length);
}
}
}
public void pre() {
if (isTiling) {
chooseTile(tileID);
}
}
public void save(String path, String baseName, String format) {
(new File(path)).mkdirs();
this.fileName = path + "/" + baseName;
this.format = format;
buffer = new PImage(gfx.width * numTiles, gfx.height * numTiles);
tileID = 0;
subTileID = 0;
isTiling = true;
}
public boolean isTiling() {
return isTiling;
}
}