Sunday, April 3, 2022

Codejam Chain Reactions

Introduction

This article describes a Java API to run a Depth-First-Search traversal. It lets you customize node processing via a node visitor.


Problem

In Code Jam Chain Reactions problem, we need to implement DFS to compute a root node value. It is derived from its children values in a recursive fashion.


Constraints

  • 1 ≤ T ≤ 10^2
  • 1 ≤ N ≤ 10^5

Input Parsing


Nodes are indexed 1 through N. We assign index 0 to the abyss, which acts as a sink.

package codejam.qualification2022;

import graph.tree.traversal.DFSTraversal;
import graph.tree.traversal.NodeVisitor;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class ChainReactions {
public static void main(String[] args) throws Exception {
//InputStream inputStream = System.in;
InputStream inputStream = new FileInputStream("ChainReactions");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
PrintWriter writer = new PrintWriter(new BufferedOutputStream(System.out));

String[] tokens;
tokens = bufferedReader.readLine().split(" ");
int T = Integer.parseInt(tokens[0]);
for (int t = 1; t <= T; t++) {
tokens = bufferedReader.readLine().split(" ");
int N = Integer.parseInt(tokens[0]);
tokens = bufferedReader.readLine().split(" ");
int[] F = new int[N+1];
for (int i = 1; i <= N; i++) {
F[i] = Integer.parseInt(tokens[i-1]);
}
tokens = bufferedReader.readLine().split(" ");
int[] P = new int[N+1];
for (int i = 1; i <= N; i++) {
P[i] = Integer.parseInt(tokens[i-1]);
}
ChainReactions chainReactions = new ChainReactions();
writer.println(String.format("Case #%d: %s", t, chainReactions.maxFun(N, F, P)));
}
writer.close();
inputStream.close();
} }


Input


3
4
60 20 40 50
0 1 1 2
5
3 2 1 4 5
0 1 1 1 0
8
100 100 100 90 80 100 90 100
0 1 2 1 2 3 1 3


Output


Case #1: 110
Case #2: 14
Case #3: 490


Adjacency List


Before running graph traversal, we need to build adjacency list from parent P array.


private List<List<Integer>> getAdjacency(int N, int[] P) {
List<List<Integer>> adjacency = new ArrayList<>();
for (int i = 0; i <= N; i++) {
adjacency.add(new ArrayList<>());
}
for (int i = 1; i <= N; i++) {
adjacency.get(P[i]).add(i);
}
return adjacency;
}


It defines a tree structure, which is a directed acyclic connected graph. No cycles are present per the assumption that a node of index i can only have a parent of index j < i.  Arrow direction is reverted in the traversal compared to the problem description: In the adjacency list, a node always points to its list of children.

From the problem description,

Each module may point at one other module with a lower index.


Solution


We add each cluster maxFun values..




Clusters are paths of nodes starting at any leaves, delimited optimally to maximize the sum of cluster fun attribute.

private static final int ROOT = 0;

public long maxFun(int N, int[] F, int[] P) {
List<List<Integer>> adjacency = getAdjacency(N, P);

FunNodeVisitor visitor = new FunNodeVisitor();
visitor.F = F;
visitor.maxFun = new int[N+1];

visitor.fun = 0;
new DFSTraversal().traverse(adjacency, visitor);
visitor.fun += visitor.maxFun[ROOT];

return visitor.fun;
}


Node visitor


We define following interface


package graph.tree.traversal;

import java.util.List;

public interface NodeVisitor {
void visit(int parent, List<Integer> children);
}

In the solution's implementation, we sum up the maxFun values of each children, except the child with the minimum value. In this case we assign the max of current nodes's F value and the min child maxFun value.

static class FunNodeVisitor implements NodeVisitor {
int[] F;
int[] maxFun;
long fun;

public void visit(int parent, List<Integer> children) {
// compute node value bottom-up
int minChild = minFunChild(children, maxFun);
maxFun[parent] = Math.max(minChild == -1 ? 0 : maxFun[minChild], F[parent]);

fun += children.stream()
.filter(child -> child != minChild)
.mapToLong(child -> maxFun[child])
.sum();
}

private int minFunChild(List<Integer> children, int[] maxFun) {
return children.stream()
.min(Comparator.comparingInt(child -> maxFun[child]))
.orElse(-1);
}
}


DFS traversal


We define a general traversal interface:

package graph.tree.traversal;

import java.util.List;

public interface Traversal {
void traverse(List<List<Integer>> adjacency, NodeVisitor nodeVisitor);
}


Traversal will explore all the nodes in the adjacency list.


Algorithm


A DFS reference can be found in CLRS, chapter 22, section 3.



Exercise 22.3-6 asks for the Stack implementation,

Rewrite the procedure DFS, using a stack to eliminate recursion.


Implementation


See this commit for the full implementation details.

package graph.tree.traversal;

import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
* Simulate a Depth-First-Search Postorder traversal
* Use a LIFO queue where node elements are visited twice:
* - 1st time to explore the children top-down
* - 2nd time to provide a bottom-up node processing
*/
public class DFSTraversal implements Traversal {

public void traverse(List<List<Integer>> adjacency, NodeVisitor nodeVisitor) {
int N = adjacency.size();

// Use a color attribute to visit each node twice during the queue processing
// It also helps to discover connected components and detect loops
Color[] color = new Color[N];
for (int i = 0; i < N; i++) {
color[i] = Color.WHITE;
}

for (int i = 0; i < N; i++) {
if (color[i] == Color.WHITE) {
// find the connected component
traverse(adjacency, i, nodeVisitor, color);
}
}
}

private void traverse(List<List<Integer>> adjacency, int root, NodeVisitor nodeVisitor, Color[] color) {
Deque<Integer> deque = new LinkedList<>();
deque.addLast(root);

while (! deque.isEmpty()) {
// peek only on 1st visit, remove at the end of 2nd visit
int parent = deque.getLast();
List<Integer> c = adjacency.get(parent);
if (color[parent] == Color.WHITE) {
color[parent] = Color.GRAY;

// explore tree top-down
c.stream()
.filter(child -> color[child] == Color.WHITE) // avoid infinite loop
.forEach(deque::addLast);
} else {
// process node bottom up
nodeVisitor.visit(parent, c);

color[parent] = Color.BLACK;
// poll on 2nd visit
deque.removeLast();
}
}
}
}


We use a Stack / LIFO queue to simulate DFS. This removes the tree depth limitation associated to recursion. Otherwise depending on your input you may run into stack overflow error. The LIFO order is generated via addLast / getLast / removeLast in Deque interface. We instantiate LinkedList class as our interface implementation choice.

The traversal notifies NodeVisitor callback on every node once. The are visited twice in the LIFO queue. We leverage the node color assignment to decide whether to explore or to backtrack.

  1. Nodes are explored by adding them at the end of queue during the 1st top-down pass.
  2. We can finally compute the parent value from its already computed children values, bottom-up, in the 2nd bottom-up pass. This logics is delegated to the NodeVisitor.

Unit Test


package graph.tree.traversal;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

import static java.util.Arrays.asList;

public class DFSTraversalTest {

private final static Logger LOG = LoggerFactory.getLogger(DFSTraversalTest.class);

private final DFSTraversal dfsTraversal = new DFSTraversal();

@Test
public void dfs() {
List<List<Integer>> adjacency = new ArrayList<>();
adjacency.add(0, asList(1));
adjacency.add(1, asList(2, 3));
adjacency.add(2, asList(4));
adjacency.add(3, asList());
adjacency.add(4, asList());

List<Integer> nodes = new ArrayList<>();
dfsTraversal.traverse(adjacency, (parent, children) -> {
nodes.add(parent);
LOG.info(String.format("%d: %s", parent, children));
});
Assertions.assertEquals(asList(3, 4, 2, 1, 0), nodes, "Post-order traversal");
}
}


Here's the test logging output:


3: []
4: []
2: [4]
1: [2, 3]
0: [1]


Notice the last leaf is visited first, backtracking the post-order all the way back to the root.


Conclusion


Sometimes you just want to build on top of the construct without dealing with the implementation details. Once you know what type of traversal you need (DFS vs BFS), there are multiple benefits to get from those reusable components:

  • focus on custom node value computation logics to solve the problem at hand
  • use a well-tested, reusable and extensible implementation
  • reduce the risk of bugs associated to remembering the text book algorithm details
  • save time by not having to re-code the DFS implementation

Appendix: Graphviz input


Graph 1


digraph G {

  subgraph cluster0 {

    label="60"

    labeljust=r

    0 -> 1

    1 -> 3

  }

  1 -> 2

  subgraph cluster2 {

    label="50"

    labeljust=r

    2 -> 4

  }

  0 [xlabel="0"]

  1 [xlabel="60"]

  2 [xlabel="20"]

  3 [xlabel="40"]

  4 [xlabel="50"]

} 


Graph 2


digraph G {

  0 -> 1

  1 -> 2

  2 -> 4

  1 -> 3

} 


Command-line


dot -Tjpg example.dot -o example.jpeg

Thursday, March 24, 2022

Codechef SUB_XOR

Introduction

This article describes an invalid approach to Codechef SUB_XOR problem from March Long challenge 1.

If failed task #3 in the test suite:

Let's find out why.


Problem statement

We map a string, containing only '0' or '1' characters, to the number it represents in binary format. 
Given such a binary string, find the XOR value of all its binary substrings, modulo 998 244 353.


Constraints

  • 1 <= N <= 10^5
  • 1 <= T <= 10^2


Input Parsing

package codechef.mar22;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;

class SubarrayXOR {
private static final int MOD = 998_244_353;

public static void main(String[] args) throws Exception {
//InputStream inputStream = System.in;
InputStream inputStream = new FileInputStream("SUB_XOR");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
PrintWriter writer = new PrintWriter(new BufferedOutputStream(System.out));

String[] tokens;
tokens = bufferedReader.readLine().split(" ");
int T = Integer.parseInt(tokens[0]);
while (T > 0) {
tokens = bufferedReader.readLine().split(" ");
int N = Integer.parseInt(tokens[0]);
tokens = bufferedReader.readLine().split(" ");
String S = tokens[0];

SubarrayXOR subarrayXOR = new SubarrayXOR();
writer.println(subarrayXOR.beauty(S));
T--;
}
writer.close();
inputStream.close();
}
}


Input

Here's an input sample:
3
2
10
3
101
4
1111


Output

Here's the output:
3
6
12


Brute-force solution

public int beauty2(String S) {
int N = S.length();
int xor = 0;
for (int n = 1; n <= N; n++) {
for (int i = 0; i < N-n+1; i++) {
int subStringNumber = toNumber(S, i, n);
xor ^= subStringNumber;
}
}
return xor;
}

private int toNumber(String S, int i, int n) {
int number = 0;
for (int j = i+n-1; j >= i; j--) {
if (S.charAt(j) == '1') {
number += 1 << (i+n-1 - j);
}
}
return number;
}

For all substring sizes n1 <= n <= N, convert the substring of size n starting at index i , 0 <= i < N-n+1 to the associated number and apply xor operation.

Make sure to scan the characters from right to left by starting with the last character, since strings are parsed left to right.

Complexity is O(N^3), which will timeout.


My solution

private static final ModularArithmetics MODULAR_ARITHMETICS = new ModularArithmetics(MOD);

public int beauty(String S) {
int N = S.length();

int count = 0;
int sum = 0;
int[] c = new int[N];
int[] s = new int[N];
for (int i = 0; i < N; i++) {
s[i] = sum;
boolean bit = S.charAt(i) == '1';
if (bit) {
count++;
}
sum = MODULAR_ARITHMETICS.add(sum, count);
c[i] = count;
}

int beauty = 0;
for (int i = 0; i < N; i++) {
int xor = MODULAR_ARITHMETICS.substract(MODULAR_ARITHMETICS.multiply(N-i, c[N-1-i]), s[N-1-i]);
beauty = MODULAR_ARITHMETICS.add(beauty, (xor % 2) * MODULAR_ARITHMETICS.exponent(2, i));
}
return beauty;
}
ModularArithmetics is a utility to add, substract, multiply and exponentiate values modulo a prime number. See appendix.

We estimate the number of substrings with a '1' character contributing to bit i, 0 <= i < N, where N is the input string size. This number is denoted cnt_i in the editorial. If it's odd we enable the i-th bit in the output value.

I applied following formula:

cnt_i = (N-i) * c[N-1-i] - s[N-1-i]

with

  • c[j] = Sum_{S[i], 0 <= i <= j}
  • s[j] = Sum_{c[i], 0 <= i < j}

It leverages prefix count and sum arrays defined above.

Complexity is O(N) for both runtime and space complexities.

I have highlighted in italic 3 problematic lines of code above. Compare with fixed solution section below.


Editorial solution

See editorial for a description of the solution.
public int beauty3(String S) {
int N = S.length();
long[] cnt = new long[N];
for(int i = 0; i< N; i++)
if(S.charAt(i) == '1')
cnt[N-i-1] += (1+i);//Adding contribution of on bits

for(int i = N-2; i>= 0; i--)
cnt[i] += cnt[i+1]; // Taking suffix sum to recover cnt

//Converting cnt to decimal number
long ans = 0, f = 1;
for(int i = 0; i < N; i++){
cnt[i] %= 2; // Only the parity of count matters
ans += f*cnt[i]%MOD;
if(ans >= MOD)
ans -= MOD;
f = (f*2)%MOD;
}
return (int) ans;
}
One quick win when comparing my approach with the editorial:

To compute 2^i [m], 0 <= i < N, just multiply by 2 and apply mod operation in O(1), instead of doing modular exponentiation in O(log(N)).

But it's not obvious what went wrong in my approach.


Generating test data

We can use the editorial implementation above as a benchmark to troubleshoot the bug in our solution.
private static final int MAX_N = 100_000;

public void generateTests() {
Random random = new Random();
while (true) {
int N = 1 + random.nextInt(MAX_N);
char[] c = new char[N];
for (int i = 0; i < N; i++) {
c[i] = random.nextBoolean() ? '1' : '0';
}

String S = new String(c);
int b1 = beauty(S);
int b3 = beauty3(S);
if (b1 != b3) {
System.err.println(S);
break;
}
}
}

We come up with an input where our approach and the editorial's diverge.

We generate binary strings randomly, compare the results of both implementations on the same input string until we find a discrepancy.


Bugfix

Compare cnt array is long type in editorial solution, while s array in my solution is int type with mod operations *, +, -.

The bug was to have computed the number modulo the prime number and use int rather than long. The overflow was avoided by applying mod operation. But the real problem is to add/subtract an odd number of times MOD odd value on the final value: this would change the parity should the count go higher than the modulo value or go negative.

cnt_i value may be higher than MOD.

On a reproduction case, I had

cnt[0] = 1 086 784 617
           =     88 540 264 [998 244 353] 

so the parity got changed after modulo operation.


Fixed Solution

public int beauty(String S) {
int N = S.length();

int count = 0;
int sum = 0;
int[] c = new int[N];
long[] s = new long[N];
for (int i = 0; i < N; i++) {
s[i] = sum;
boolean bit = S.charAt(i) == '1';
if (bit) {
count++;
}
sum += count;
c[i] = count;
}

int beauty = 0;
for (int i = 0; i < N; i++) {
long bitCount = 1L * (N-i) * c[N-1-i] - s[N-1-i];
beauty = MODULAR_ARITHMETICS.add(beauty, ((int) (bitCount % 2)) * MODULAR_ARITHMETICS.exponent(2, i));
}
return beauty;
}
Notice we now compute exact values for the number for the bit count.
The mod operation was only required to fit in an int value on large number of bits, while keeping the correct counts within long type.

Another callout is that ModularArithmetics utility seems overkill to just add 2 numbers or compute power of 2.

See new successful submission.




Conclusion

MOD = 998 244 353 is a large prime number hence odd number, you should not add / subtract in modular arithmetics for exact parity computation. You should compute exact values with long type should there be overflow with int.


Appendix

ModularArithmetics implementation is

/**
* Modular arithmetics.
*/
public class ModularArithmetics {

private final int m;
public ModularArithmetics(int m) {
this.m = m;
}

/**
* Right-to-left binary method.
*
* @param b Base
* @param e Exponent
* @return b^e [m]
*/
public int exponent(int b, int e) {
int pow = 1;

int base = b % m;
int exponent = e;

while (exponent > 0) {
if ((exponent & 1) != 0) {
pow = multiply(pow, base);
}
exponent >>= 1;
base = multiply(base, base);
}

return pow;
}

/**
* Handles overflow.
*
* @param a
* @param b
* @return a * b % m
*/
public int multiply(int a, int b) {
return (int) ((1L * a * b) % m);
}

/**
*
* @param a
* @return a^2 % m
*/
public int square(int a) {
return multiply(a, a);
}

/**
*
* @param a
* @param b
* @return a + b % m
*/
public int add(int a, int b) {
return (a + b) % m;
}

/**
*
* @param a
* @param b
* @return a - b % m
*/
public int substract(int a, int b) {
return positive(a-b);
}

/**
*
* @param a
* @return same value as a but positive
*/
private int positive(int a) {
return (a % m + m) % m;
}

/**
*
* @param a
* @return u such that a * u = 1
*/
public int inverse(int a) {
// a * u + m * v = 1, m is prime
int u = bezoutCoefficient(a, m);
return positive(u);
}

/**
* u such that
* a * u + b * v = gcd(a, b)
*
* @param a
* @param b
* @return
*/
private int bezoutCoefficient(int a, int b) {
int s = 0, old_s = 1;
int t = 1, old_t = 0;
int r = b, old_r = a;
int prov;

while (r != 0) {
int q = old_r / r;

prov = r;
r = old_r - q * prov;
old_r = prov;

prov = s;
s = old_s - q * prov;
old_s = prov;

prov = t;
t = old_t - q * prov;
old_t = prov;
}

return old_s;
}
}



Saturday, August 12, 2017

Java Parser Benchmark

Intro

I ran into slow parsing issues when loading this Hackerrank problem input. When parsing 1M integers, you can no longer rely on high level library such as java.util.Scanner as they are not optimized to be fast. You can easily replace this handy yet slow utility by a lower level function that wraps simple String.split / Integer.parseInt operations.

We also compare loading file from disk using JVM Heap buffer vs native, off-Heap buffer. We also look at the encoding overhead to convert binary data to UTF-8 characters required before manipulating strings. This will show the benefits of just converting bytes to int data to achieve the best performance.

Maven Project

The code is bundled in this SameOccurrence maven project. The input is a 16 MB plain text file containing more than 1M integers to scan. This was the input of Test Case #18 in the Hackerrank challenge.

Benchmark


Antlr parser is the slowest, loading 1M integers in 3 seconds. This is due to extra overhead to generate the parse tree. Parser generators are very helpful to validate input syntax using a grammar, but are not tailored for performance.

On the other end the raw byte parser finishes in 10 ms as it does not do extra conversions / validation. It assumes the input is valid and simply converts sequence of bytes separated by whitespaces into base 10 integers.

Here we compare the time to load a file into a heap buffer, a byte array allocated within the JVM and the time to load the file using mmap system call, implemented via a native function that allocates the buffer off-heap leveraging Virtual Memory. We reduce our 20 ms loading time down to 4 ms.

The 3rd bar shows the UTF-8 conversion overhead you will need to include to start working with Strings. On top of loading raw data in 4 ms, you need to spend an additional 80 ms to just get UTF-8 data ...

Antlr

ANTLR is a parser generator written in Java that converts a grammar into a parser. It provides a maven plugin that wraps the Tool utility to parse the grammar and generate associated parser.
You implement a parse listener to load the data as rules get executed.


public void antlr() {
    CharStream charStream = CharStreams.fromString(input);

    Lexer lexer = new SameOccurrenceLexer(charStream);
    CommonTokenStream stream = new CommonTokenStream(lexer);
    SameOccurrenceParser parser = new SameOccurrenceParser(stream);
    parser.addParseListener(new SameOccurrenceParseListener());
    parser.getInterpreter().setPredictionMode(PredictionMode.LL);
    parser.r();
}

Scanner

With the JDK Scanner it is very easy to load raw data types from an input stream into memory.

public void scan() {
    Scanner scanner = new Scanner(input);
    int n = scanner.nextInt();
    int q = scanner.nextInt();
    int[] a = new int[n];
    for (int i = 0; i < n; i++) {
        a[i] = scanner.nextInt();
    }
    for (int t = 0; t < q; t++) {
        int x = scanner.nextInt();
        int y = scanner.nextInt();
    }
    scanner.close();
}

Split

With the JDK String split method, you can break down your String input into tokens using a regex separator. Then convert string to int using Integer.parseInt. Returning raw data type is faster than Integer object via Integer.valueOf.

int tokenOffset = 0;
public void split() {
    String[] tokens = input.split("\\s+");
    int n = parseToken(tokens);
    int q = parseToken(tokens);
    int[] a = new int[n];
    for (int i = 0; i < n; i++) {
        a[i] = parseToken(tokens);
    }
    for (int t = 0; t < q; t++) {
        int x = parseToken(tokens);
        int y = parseToken(tokens);
    }
    assert tokenOffset == tokens.length;
}

private int parseToken(String[] tokens) {
    return Integer.parseInt(tokens[tokenOffset++]);
}

Raw

You just scan for the next whitespace delimiter, then converts the byte sequence to an integer. This is the same logics as Integer.parseInt implementation.

int byteOffset = 0;
public void raw() {
    int n = parseInt();
    int q = parseInt();
    int[] a = new int[n];
    for (int i = 0; i < n; i++) {
        a[i] = parseInt();
    }
    for (int t = 0; t < q; t++) {
        int x = parseInt();
        int y = parseInt();
    }
}

private static final int BASE = 10;
private static final char ZERO = '0';
private int parseInt() {
    int start = byteOffset;
    int end = getTokenEnd();

    int i = 0;
    int pow = 1;
    for (int pos = end; pos >= start; pos--) {
        int digit = byteBuffer.get(pos) - ZERO;
        i += pow * digit;
        pow *= BASE;
    }
    return i;
}

private int getTokenEnd() {
    while (isWhitespace(byteBuffer.get(byteOffset))) {
        byteOffset++;
    }
    int pos = byteOffset;
    while(! isWhitespace(byteBuffer.get(pos))) {
        pos++;
    }
    byteOffset = pos;
    return pos-1;
}

private static final char LF = '\n';
private static final char CR = '\r';
private static final char SPACE = ' ';
private boolean isWhitespace(byte b) {
    return b == LF || b == CR || b == SPACE;
}

Sunday, May 4, 2014

DNG 1.4 Parser

This tutorial describes how to build the Adobe DNG SDK on Linux.
It generates the dng_validate C++ program that can parse any DNG images, a bit like a "Hello world" for DNG image processing.


  1. Adobe DNG SDK 1.4


Adobe DNG SDK 1.4


The goal of this blog entry is to execute the dng_validate command built from the source.
We show how to build the XMP libraries then suggest a Makefile configuration to build a DNG SDK sample app.
All the patches were generated by the diff command.

XMP SDK


In the current directory, download the XMP SDK:
wget http://download.macromedia.com/pub/developer/xmp/sdk/XMP-Toolkit-SDK-CC-201306.zip
unzip XMP-Toolkit-SDK-CC-201306.zip
mv XMP-Toolkit-SDK-CC201306 xmp_sdk

Make sure you have at least the following packages installed:

sudo apt-get install cmake libjpeg8-dev uuid-dev

The 2 next snippets download zlib and expat sources. Instructions are explained in the ReadMe.txt files in the third-party directories.

zlib


cd xmp_sdk/third-party/zlib
wget http://zlib.net/zlib-1.2.8.tar.gz
tar xzf zlib-1.2.8.tar.gz
cp zlib-1.2.8/*.h zlib-1.2.8/*.c .

expat


cd xmp_sdk/third-party/expat
Download here the source at http://sourceforge.net/projects/expat/files/expat/2.1.0/expat-2.1.0.tar.gz/download.
tar xzf expat-2.1.0.tar.gz
cp -R expat-2.1.0/lib .

For cross-platform compatibility, to be able to support both Mac & Windows, cmake is the tool selected by the SDK authors to build the source. We go through all the build errors.

cd xmp_sdk/build

Build error 1


make
...
CMake Error at shared/SharedConfig_Common.cmake:38 (if):
  if given arguments:

    "LESS" "413"

  Unknown arguments specified
...

I use gcc 4.8.2 for the build. There seems to be a XMP_VERSIONING_GCC_VERSION variable not properly set. We just delete the version check by removing line 37 till 42 in xmp_sdk/build/shared/SharedConfig_Common.cmake:

xmp_sdk/build/shared/SharedConfig_Common.cmake
36a37,41
>               # workaround for visibility problem and gcc 4.1.x
>               if(${${COMPONENT}_VERSIONING_GCC_VERSION} LESS 413)
>                       # only remove inline hidden...
>                       string(REGEX REPLACE "-fvisibility-inlines-hidden" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
>               endif()

Build error 2


make
...
Linking CXX shared library /home/alexis/linux/dng2/xmp_sdk/public/libraries/i80386linux_x64/release/libXMPCore.so
g++: error: /usr/lib64/gcc/x86_64-redhat-linux/4.4.4//libssp.a: No such file or directory
...

The README.txt mentions a XMP_ENABLE_SECURE_SETTINGS:

xmp_sdk/build/README.txt
... He may also want to change the parameter XMP_ENABLE_SECURE_SETTINGS as per the configured gcc.
 a) If the gcc is configured with --enable-libssp (can be checked by executing gcc -v), he has to set the variable XMP_GCC_LIBPATH inside of file /build/XMP_Linux.cmake to the path containing the static lib( libssp.a).In this case he can set the variable the XMP_ENABLE_SECURE_SETTINGS on.
b) If the gcc is configured with --disable-libssp, he has to set the variable XMP_ENABLE_SECURE_SETTINGS off.

I don't have any --enable-libssp in my gcc version:

$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.8.2-21' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --with-arch-32=i586 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.2 (Debian 4.8.2-21)

so I disable it in xmp_sdk/build/shared/ToolchainGCC.cmake line 37:

xmp_sdk/build/shared/ToolchainGCC.cmake
37c37
< set(XMP_ENABLE_SECURE_SETTINGS "OFF")
---
> set(XMP_ENABLE_SECURE_SETTINGS "ON")

Build error 3


/home/alexis/linux/dng2/xmp_sdk/XMPFiles/build/../../XMPFiles/source/NativeMetadataSupport/ValueObject.h:111:60: error: 'memcmp' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
    doSet = ( memcmp( mArray, buffer, numElements*sizeof(T) ) != 0 );
                                                            ^
In file included from /home/alexis/linux/dng2/xmp_sdk/XMPFiles/build/../../XMPFiles/source/FormatSupport/TIFF_Support.hpp:17:0,
                 from /home/alexis/linux/dng2/xmp_sdk/XMPFiles/build/../../XMPFiles/source/FormatSupport/ReconcileLegacy.hpp:15,
                 from /home/alexis/linux/dng2/xmp_sdk/XMPFiles/build/../../XMPFiles/source/FormatSupport/Reconcile_Impl.hpp:15,
                 from /home/alexis/linux/dng2/xmp_sdk/XMPFiles/source/FormatSupport/WAVE/WAVEReconcile.cpp:24:
/usr/include/string.h:65:12: note: 'int memcmp(const void*, const void*, size_t)' declared here, later in the translation unit
 extern int memcmp (const void *__s1, const void *__s2, size_t __n)

This is an error relative to my new version of gcc, described in the Name lookup changes section.
We simply add the #include "string.h" in xmp_sdk/XMPFiles/source/NativeMetadataSupport/ValueObject.h, line 16:

xmp_sdk/XMPFiles/source/NativeMetadataSupport/ValueObject.h
16,17d15
< #include "string.h"

Validate the build


make
[100%] Built target XMPFilesStatic

Check that the shared and static libraries got properly generated:

$ ls xmp_sdk/public/libraries/i80386linux_x64/release
libXMPCore.so*  libXMPFiles.so*  staticXMPCore.ar  staticXMPFiles.ar

DNG SDK


In the current directory, where the XMP SDK got downloaded, download the DNG SDK:

wget http://download.adobe.com/pub/adobe/dng/dng_sdk_1_4.zip
unzip dng_sdk_1_4.zip

There are multiple ways to come up with the binary. In this case,
  • We seperate the compilation and linking steps in 2 separate targets.
  • We link with the shared libraries of the XMP Toolkit.
  • We did not install system wide in /usr/local/lib and /usr/local/include the required XMP files that were generated during the XMP build.

cd dng_sdk/source

Makefile


Create the "Makefile" build configuration file:

# Binary name
EXECUTABLE=dng_validate

# A DNG image
DNG_IMAGE=~/job/image_samples/9436b5a2336f0a575a5b0ef3adf0b25171125081.dng

# The XMP SDK build directory if we don't want to install it system-wide.
XMP_PUB_DIR=/home/alexis/linux/dng/xmp_sdk/public

INCL=-I $(XMP_PUB_DIR)/include
XMP_RELEASE=$(XMP_PUB_DIR)/libraries/i80386linux_x64/release
LIB=-ljpeg -lz -lpthread -ldl -L $(XMP_RELEASE) -lXMPCore -lXMPFiles
SOURCES:=$(shell ls *.cpp)
OBJECTS=$(SOURCES:.cpp=.o)

# Execute the binary
all: $(EXECUTABLE) $(SOURCES)
        LD_LIBRARY_PATH=$(XMP_RELEASE) ./$< -v $(DNG_IMAGE)

# Linking
$(EXECUTABLE): $(OBJECTS)
        g++ $^ $(LIB) -o $@

# Compilation
.cpp.o:
        g++ -c -Wall -g $(INCL) $^

clean:
        rm $(EXECUTABLE) *.o

Build error 1


To avoid errors like

dng_flags.h:36:28: fatal error: RawEnvironment.h: No such file or directory
 #include "RawEnvironment.h"

Create the RawEnvironment.h file containing build settings:

#define qLinux 1
#define qDNGThreadSafe 1
#define UNIX_ENV 1

Build error 2


To avoid errors like

dng_flags.h:40:2: error: #error Unable to figure out platform

A qLinux setting got created in RawEnvironment.h. The "!defined(qMacOS) || !defined(qWinOS)" kind of test line 39 in dng_flags.h does not seem effective on Linux platform even when qMacOS or qWinOS are defined. We replace it by something more standard, for example "#ifndef qLinux":

39 #ifndef qLinux
40 #error Unable to figure out platform
41 #endif


chmod +w dng_flags.h
39c39
< #ifndef qLinux
---
> #if !defined(qMacOS) || !defined(qWinOS)

Build error 3


dng_string.cpp:1792:24: error: 'isdigit' was not declared in this scope
    if (isdigit ((int) c) || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')

We simply need to include "ctype.h" in dng_string.cpp for linux platform at line 33:

32 #if qiPhone || qAndroid || qLinux
33 #include <ctype.h> // for isdigit
34 #endif

At line 32 we add qLinux is the list of platforms on when to include ctype.h.

chmod +x dng_string.cpp
32c32
< #if qiPhone || qAndroid || qLinux
---
> #if qiPhone || qAndroid

Build error 4


/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'

The main function declared in dng_validate.cpp was not found. Enable the command build setting in line 200 in dng_flags.h:

200c200
< #define qDNGValidateTarget 1
---
> #define qDNGValidateTarget 0

Build error 5


make clean
make

We now have a linking error

dng_xmp_sdk.cpp:(.text._ZN9TXMPFilesISsE8OpenFileEP6XMP_IOjj[_ZN9TXMPFilesISsE8OpenFileEP6XMP_IOjj]+0x40): undefined reference to `WXMPFiles_OpenFile_2'
collect2: error: ld returned 1 exit status

We want to link with the shared libraries. Delete the static build setting in dng_xmp_sdk.cpp line 48:

chmod +w dng_xmp_sdk.cpp
48a49,50
> #define XMP_StaticBuild 1
> 

Validate the build


The execution of the command looks like:

make
...
LD_LIBRARY_PATH=/home/alexis/linux/dng/xmp_sdk/public/libraries/i80386linux_x64/release ./dng_validate ~/job/image_samples/9436b5a2336f0a575a5b0ef3adf0b25171125081.dng
Validating "/home/alexis/job/image_samples/9436b5a2336f0a575a5b0ef3adf0b25171125081.dng"...
Raw image read time: 0.074 sec
Linearization time: 0.051 sec
Interpolate time: 0.000 sec
Validation complete