Repository: pymc-devs/pymc

Issue:
The model string representation fails if there are unnamed RVs in the graph
### Describe the issue:

If a model defines components that use pytensor random variables that don't have a name, trying to get the model's string representation raises an error.

### Reproduceable code example:

```python
import pymc as pm

with pm.Model() as m:
    a = pm.Deterministic("a", pm.Normal.dist())
m.str_repr()
```


### Error message:

```shell
<details>
----> 1 m.str_repr()

File ~/repos/pymc/pymc/printing.py:109, in str_for_model(model, formatting, include_params)
    107 free_rv_reprs = [sfd(dist) for dist in model.free_RVs]
    108 observed_rv_reprs = [sfd(rv) for rv in model.observed_RVs]
--> 109 det_reprs = [sfp(dist, dist_name="Deterministic") for dist in model.deterministics]
    110 potential_reprs = [sfp(pot, dist_name="Potential") for pot in model.potentials]
    112 var_reprs = free_rv_reprs + det_reprs + observed_rv_reprs + potential_reprs

File ~/repos/pymc/pymc/printing.py:109, in <listcomp>(.0)
    107 free_rv_reprs = [sfd(dist) for dist in model.free_RVs]
    108 observed_rv_reprs = [sfd(rv) for rv in model.observed_RVs]
--> 109 det_reprs = [sfp(dist, dist_name="Deterministic") for dist in model.deterministics]
    110 potential_reprs = [sfp(pot, dist_name="Potential") for pot in model.potentials]
    112 var_reprs = free_rv_reprs + det_reprs + observed_rv_reprs + potential_reprs

File ~/repos/pymc/pymc/printing.py:156, in str_for_potential_or_deterministic(var, formatting, include_params, dist_name)
    154 else:  # plain
    155     if include_params:
--> 156         return rf"{print_name} ~ {dist_name}({_str_for_expression(var, formatting=formatting)})"
    157     else:
    158         return rf"{print_name} ~ {dist_name}"

File ~/repos/pymc/pymc/printing.py:235, in _str_for_expression(var, formatting)
    233         parents.append(x)
    234         xname = x.name
--> 235         assert xname is not None
    236         names.append(xname)
    238 if "latex" in formatting:

AssertionError:
</details>
```


### PyMC version information:

5.16.2

### Context for the issue:

_No response_

Discussion:

Patch:
diff --git a/pymc/printing.py b/pymc/printing.py
index 6695cf38f..56445ab9e 100644
--- a/pymc/printing.py
+++ b/pymc/printing.py
@@ -232,6 +232,12 @@ def _str_for_expression(var: Variable, formatting: str) -> str:
         if x.owner and isinstance(x.owner.op, RandomVariable | SymbolicRandomVariable):
             parents.append(x)
             xname = x.name
+            if xname is None:
+                # If the variable is unnamed, we show the op's name as we do
+                # with constants
+                opname = x.owner.op.name
+                if opname is not None:
+                    xname = rf"<{opname}>"
             assert xname is not None
             names.append(xname)
 


Test patch:
diff --git a/tests/test_printing.py b/tests/test_printing.py
index 95a1e812e..406032b12 100644
--- a/tests/test_printing.py
+++ b/tests/test_printing.py
@@ -125,8 +125,11 @@ class TestMonolith(BaseTestStrAndLatexRepr):
             # add a potential as well
             pot = Potential("pot", mu**2)
 
+            # add a deterministic that depends on an unnamed random variable
+            pred = Deterministic("pred", Normal.dist(0, 1))
+
         self.distributions = [alpha, sigma, mu, b, Z, nb2, zip, w, nested_mix, Y_obs, pot]
-        self.deterministics_or_potentials = [mu, pot]
+        self.deterministics_or_potentials = [mu, pot, pred]
         # tuples of (formatting, include_params)
         self.formats = [("plain", True), ("plain", False), ("latex", True), ("latex", False)]
         self.expected = {
@@ -146,6 +149,7 @@ class TestMonolith(BaseTestStrAndLatexRepr):
                 ),
                 r"Y_obs ~ Normal(mu, sigma)",
                 r"pot ~ Potential(f(beta, alpha))",
+                r"pred ~ Deterministic(f(<normal>))",
             ],
             ("plain", False): [
                 r"alpha ~ Normal",
@@ -159,6 +163,7 @@ class TestMonolith(BaseTestStrAndLatexRepr):
                 r"nested_mix ~ MarginalMixture",
                 r"Y_obs ~ Normal",
                 r"pot ~ Potential",
+                r"pred ~ Deterministic",
             ],
             ("latex", True): [
                 r"$\text{alpha} \sim \operatorname{Normal}(0,~10)$",
@@ -176,6 +181,7 @@ class TestMonolith(BaseTestStrAndLatexRepr):
                 ),
                 r"$\text{Y_obs} \sim \operatorname{Normal}(\text{mu},~\text{sigma})$",
                 r"$\text{pot} \sim \operatorname{Potential}(f(\text{beta},~\text{alpha}))$",
+                r"$\text{pred} \sim \operatorname{Deterministic}(f(\text{<normal>}))",
             ],
             ("latex", False): [
                 r"$\text{alpha} \sim \operatorname{Normal}$",
@@ -189,6 +195,7 @@ class TestMonolith(BaseTestStrAndLatexRepr):
                 r"$\text{nested_mix} \sim \operatorname{MarginalMixture}$",
                 r"$\text{Y_obs} \sim \operatorname{Normal}$",
                 r"$\text{pot} \sim \operatorname{Potential}$",
+                r"$\text{pred} \sim \operatorname{Deterministic}",
             ],
         }