Repository: scikit-hep/pyhf

Issue:
`pyhf json2xml` requires parameter inits
### Summary

When converting a workspace via `pyhf json2xml`, the model config seems to rely on the presence of parameter inits, even though the spec does not strictly require them.

### OS / Environment

```console
n/a
```


### Steps to Reproduce

workspace:
```json
{
    "channels": [
        {
            "name": "ch",
            "samples": [
                {
                    "data": [1000.0],
                    "modifiers": [
                        {"data": null, "name": "mu_sig", "type": "normfactor"},
                        {
                            "data": {"hi": 1.5, "lo": 0.5},
                            "name": "unc",
                            "type": "normsys"
                        }
                    ],
                    "name": "signal"
                }
            ]
        }
    ],
    "measurements": [
        {
            "config": {
                "parameters": [
                    {
                        "name": "mu_sig",
                        "bounds": [[0, 10]]
                    }
                ],
                "poi": "mu_sig"
            },
            "name": "meas"
        }
    ],
    "observations": [{"data": [1000], "name": "ch"}],
    "version": "1.0.0"
}

```
convert with `pyhf json2xml`:
```pytb
...
  File "[...]/pyhf/src/pyhf/cli/rootio.py", line 88, in json2xml
    writexml.writexml(
  File "[...]/pyhf/src/pyhf/writexml.py", line 292, in writexml
    channel = build_channel(spec, channelspec, spec.get('observations'))
  File "[...]/pyhf/src/pyhf/writexml.py", line 271, in build_channel
    channel.append(build_sample(spec, samplespec, channelspec['name']))
  File "[...]/pyhf/src/pyhf/writexml.py", line 245, in build_sample
    modifier = build_modifier(
  File "[...]/pyhf/src/pyhf/writexml.py", line 181, in build_modifier
    val = p['inits'][0]
KeyError: 'inits'
```
A fit with `pyhf fit` works perfectly fine. The addition of `"inits": [1.0]` in the model config also fixes the crash, as does removing this parameter configuration completely.


### File Upload (optional)

_No response_

### Expected Results

I expect the conversion to not rely on the presence of `inits`. The default of `1.0` may not make sense if some custom bounds are specified, but I think this is on the user to fix. The same issue also appears when just setting a `normfactor` to constant without specifying the `inits`.

### Actual Results

```console
File "[...]/pyhf/src/pyhf/cli/rootio.py", line 88, in json2xml
    writexml.writexml(
  File "[...]/pyhf/src/pyhf/writexml.py", line 292, in writexml
    channel = build_channel(spec, channelspec, spec.get('observations'))
  File "[...]/pyhf/src/pyhf/writexml.py", line 271, in build_channel
    channel.append(build_sample(spec, samplespec, channelspec['name']))
  File "[...]/pyhf/src/pyhf/writexml.py", line 245, in build_sample
    modifier = build_modifier(
  File "[...]/pyhf/src/pyhf/writexml.py", line 181, in build_modifier
    val = p['inits'][0]
KeyError: 'inits'
```


### pyhf Version

```console
0.6.3
```


### Code of Conduct

- [X] I agree to follow the Code of Conduct


Discussion:

Resolving patch:
diff --git a/src/pyhf/writexml.py b/src/pyhf/writexml.py
--- a/src/pyhf/writexml.py
+++ b/src/pyhf/writexml.py
@@ -178,8 +178,8 @@ def build_modifier(spec, modifierspec, channelname, samplename, sampledata):
         high = 10
         for p in spec['measurements'][0]['config']['parameters']:
             if p['name'] == modifierspec['name']:
-                val = p['inits'][0]
-                low, high = p['bounds'][0]
+                val = p.get('inits', [val])[0]
+                low, high = p.get('bounds', [[low, high]])[0]
         attrs['Val'] = str(val)
         attrs['Low'] = str(low)
         attrs['High'] = str(high)


Test patch:
diff --git a/tests/test_export.py b/tests/test_export.py
--- a/tests/test_export.py
+++ b/tests/test_export.py
@@ -428,3 +428,30 @@ def test_integer_data(datadir, mocker):
 
     channel = pyhf.writexml.build_channel(spec, channel_spec, {})
     assert channel
+
+
+@pytest.mark.parametrize(
+    "fname,val,low,high",
+    [
+        ('workspace_no_parameter_inits.json', '1', '-5', '5'),
+        ('workspace_no_parameter_bounds.json', '5', '0', '10'),
+    ],
+    ids=['no_inits', 'no_bounds'],
+)
+def test_issue1814(datadir, mocker, fname, val, low, high):
+    with open(datadir / fname) as spec_file:
+        spec = json.load(spec_file)
+
+    modifierspec = {'data': None, 'name': 'mu_sig', 'type': 'normfactor'}
+    channelname = None
+    samplename = None
+    sampledata = None
+
+    modifier = pyhf.writexml.build_modifier(
+        spec, modifierspec, channelname, samplename, sampledata
+    )
+    assert modifier is not None
+    assert sorted(modifier.keys()) == ['High', 'Low', 'Name', 'Val']
+    assert modifier.get('Val') == val
+    assert modifier.get('Low') == low
+    assert modifier.get('High') == high
diff --git a/tests/test_export/workspace_no_parameter_bounds.json b/tests/test_export/workspace_no_parameter_bounds.json
new file mode 100644
--- /dev/null
+++ b/tests/test_export/workspace_no_parameter_bounds.json
@@ -0,0 +1,37 @@
+{
+    "channels": [
+        {
+            "name": "ch",
+            "samples": [
+                {
+                    "data": [1000.0],
+                    "modifiers": [
+                        {"data": null, "name": "mu_sig", "type": "normfactor"},
+                        {
+                            "data": {"hi": 1.5, "lo": 0.5},
+                            "name": "unc",
+                            "type": "normsys"
+                        }
+                    ],
+                    "name": "signal"
+                }
+            ]
+        }
+    ],
+    "measurements": [
+        {
+            "config": {
+                "parameters": [
+                    {
+                        "name": "mu_sig",
+                        "inits": [5]
+                    }
+                ],
+                "poi": "mu_sig"
+            },
+            "name": "meas"
+        }
+    ],
+    "observations": [{"data": [1000], "name": "ch"}],
+    "version": "1.0.0"
+}
diff --git a/tests/test_export/workspace_no_parameter_inits.json b/tests/test_export/workspace_no_parameter_inits.json
new file mode 100644
--- /dev/null
+++ b/tests/test_export/workspace_no_parameter_inits.json
@@ -0,0 +1,37 @@
+{
+    "channels": [
+        {
+            "name": "ch",
+            "samples": [
+                {
+                    "data": [1000.0],
+                    "modifiers": [
+                        {"data": null, "name": "mu_sig", "type": "normfactor"},
+                        {
+                            "data": {"hi": 1.5, "lo": 0.5},
+                            "name": "unc",
+                            "type": "normsys"
+                        }
+                    ],
+                    "name": "signal"
+                }
+            ]
+        }
+    ],
+    "measurements": [
+        {
+            "config": {
+                "parameters": [
+                    {
+                        "name": "mu_sig",
+                        "bounds": [[-5, 5]]
+                    }
+                ],
+                "poi": "mu_sig"
+            },
+            "name": "meas"
+        }
+    ],
+    "observations": [{"data": [1000], "name": "ch"}],
+    "version": "1.0.0"
+}